mirror of
https://github.com/danielmiessler/Fabric.git
synced 2026-01-08 22:08:03 -05:00
Add support for Google Cloud Vertex AI as a provider to access Claude models using Application Default Credentials (ADC). This allows users to route their Fabric requests through Google Cloud Platform instead of directly to Anthropic, enabling billing through GCP. Features: - Support for Claude models (Sonnet 4.5, Opus 4.5, Haiku 4.5, etc.) via Vertex AI - Uses Google ADC for authentication (no API keys required) - Configurable project ID and region (defaults to 'global' for cost optimization) - Full support for streaming and non-streaming requests - Implements complete ai.Vendor interface Configuration: - VERTEXAI_PROJECT_ID: GCP project ID (required) - VERTEXAI_REGION: Vertex AI region (optional, defaults to 'global') Closes #1570
580 lines
18 KiB
Go
580 lines
18 KiB
Go
package core
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/danielmiessler/fabric/internal/i18n"
|
|
debuglog "github.com/danielmiessler/fabric/internal/log"
|
|
"github.com/danielmiessler/fabric/internal/plugins/ai/anthropic"
|
|
"github.com/danielmiessler/fabric/internal/plugins/ai/azure"
|
|
"github.com/danielmiessler/fabric/internal/plugins/ai/bedrock"
|
|
"github.com/danielmiessler/fabric/internal/plugins/ai/dryrun"
|
|
"github.com/danielmiessler/fabric/internal/plugins/ai/exolab"
|
|
"github.com/danielmiessler/fabric/internal/plugins/ai/gemini"
|
|
"github.com/danielmiessler/fabric/internal/plugins/ai/lmstudio"
|
|
"github.com/danielmiessler/fabric/internal/plugins/ai/ollama"
|
|
"github.com/danielmiessler/fabric/internal/plugins/ai/openai"
|
|
"github.com/danielmiessler/fabric/internal/plugins/ai/openai_compatible"
|
|
"github.com/danielmiessler/fabric/internal/plugins/ai/perplexity"
|
|
"github.com/danielmiessler/fabric/internal/plugins/ai/vertexai"
|
|
"github.com/danielmiessler/fabric/internal/plugins/strategy"
|
|
|
|
"github.com/samber/lo"
|
|
|
|
"github.com/danielmiessler/fabric/internal/plugins"
|
|
"github.com/danielmiessler/fabric/internal/plugins/ai"
|
|
"github.com/danielmiessler/fabric/internal/plugins/db/fsdb"
|
|
"github.com/danielmiessler/fabric/internal/plugins/template"
|
|
"github.com/danielmiessler/fabric/internal/tools"
|
|
"github.com/danielmiessler/fabric/internal/tools/custom_patterns"
|
|
"github.com/danielmiessler/fabric/internal/tools/jina"
|
|
"github.com/danielmiessler/fabric/internal/tools/lang"
|
|
"github.com/danielmiessler/fabric/internal/tools/youtube"
|
|
"github.com/danielmiessler/fabric/internal/util"
|
|
)
|
|
|
|
// hasAWSCredentials checks if Bedrock is properly configured by ensuring both
|
|
// AWS credentials and BEDROCK_AWS_REGION are present. This prevents the Bedrock
|
|
// client from being initialized when AWS credentials exist for other purposes.
|
|
func hasAWSCredentials() bool {
|
|
// First check if BEDROCK_AWS_REGION is set - this is required for Bedrock
|
|
if os.Getenv("BEDROCK_AWS_REGION") == "" {
|
|
return false
|
|
}
|
|
|
|
// Then check if AWS credentials are available
|
|
if os.Getenv("AWS_PROFILE") != "" ||
|
|
os.Getenv("AWS_ROLE_SESSION_NAME") != "" ||
|
|
(os.Getenv("AWS_ACCESS_KEY_ID") != "" && os.Getenv("AWS_SECRET_ACCESS_KEY") != "") {
|
|
|
|
return true
|
|
}
|
|
|
|
credFile := os.Getenv("AWS_SHARED_CREDENTIALS_FILE")
|
|
if credFile == "" {
|
|
if home, err := os.UserHomeDir(); err == nil {
|
|
credFile = filepath.Join(home, ".aws", "credentials")
|
|
}
|
|
}
|
|
if credFile != "" {
|
|
if _, err := os.Stat(credFile); err == nil {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func NewPluginRegistry(db *fsdb.Db) (ret *PluginRegistry, err error) {
|
|
ret = &PluginRegistry{
|
|
Db: db,
|
|
VendorManager: ai.NewVendorsManager(),
|
|
VendorsAll: ai.NewVendorsManager(),
|
|
PatternsLoader: tools.NewPatternsLoader(db.Patterns),
|
|
CustomPatterns: custom_patterns.NewCustomPatterns(),
|
|
YouTube: youtube.NewYouTube(),
|
|
Language: lang.NewLanguage(),
|
|
Jina: jina.NewClient(),
|
|
Strategies: strategy.NewStrategiesManager(),
|
|
}
|
|
|
|
var homedir string
|
|
if homedir, err = os.UserHomeDir(); err != nil {
|
|
return
|
|
}
|
|
ret.TemplateExtensions = template.NewExtensionManager(filepath.Join(homedir, ".config/fabric"))
|
|
|
|
ret.Defaults = tools.NeeDefaults(ret.GetModels)
|
|
|
|
// Create a vendors slice to hold all vendors (order doesn't matter initially)
|
|
vendors := []ai.Vendor{}
|
|
|
|
// Add non-OpenAI compatible clients
|
|
vendors = append(vendors,
|
|
openai.NewClient(),
|
|
ollama.NewClient(),
|
|
azure.NewClient(),
|
|
gemini.NewClient(),
|
|
anthropic.NewClient(),
|
|
vertexai.NewClient(),
|
|
lmstudio.NewClient(),
|
|
exolab.NewClient(),
|
|
perplexity.NewClient(), // Added Perplexity client
|
|
)
|
|
|
|
if hasAWSCredentials() {
|
|
vendors = append(vendors, bedrock.NewClient())
|
|
}
|
|
|
|
// Add all OpenAI-compatible providers
|
|
for providerName := range openai_compatible.ProviderMap {
|
|
provider, _ := openai_compatible.GetProviderByName(providerName)
|
|
vendors = append(vendors, openai_compatible.NewClient(provider))
|
|
}
|
|
|
|
// Sort vendors by name for consistent ordering (case-insensitive)
|
|
sort.Slice(vendors, func(i, j int) bool {
|
|
return strings.ToLower(vendors[i].GetName()) < strings.ToLower(vendors[j].GetName())
|
|
})
|
|
|
|
// Add all sorted vendors to VendorsAll
|
|
ret.VendorsAll.AddVendors(vendors...)
|
|
_ = ret.Configure()
|
|
|
|
return
|
|
}
|
|
|
|
func (o *PluginRegistry) ListVendors(out io.Writer) error {
|
|
vendors := lo.Map(o.VendorsAll.Vendors, func(vendor ai.Vendor, _ int) string {
|
|
return vendor.GetName()
|
|
})
|
|
fmt.Fprintf(out, "%s\n\n", i18n.T("available_vendors_header"))
|
|
for _, vendor := range vendors {
|
|
fmt.Fprintf(out, "%s\n", vendor)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type PluginRegistry struct {
|
|
Db *fsdb.Db
|
|
|
|
VendorManager *ai.VendorsManager
|
|
VendorsAll *ai.VendorsManager
|
|
Defaults *tools.Defaults
|
|
PatternsLoader *tools.PatternsLoader
|
|
CustomPatterns *custom_patterns.CustomPatterns
|
|
YouTube *youtube.YouTube
|
|
Language *lang.Language
|
|
Jina *jina.Client
|
|
TemplateExtensions *template.ExtensionManager
|
|
Strategies *strategy.StrategiesManager
|
|
}
|
|
|
|
func (o *PluginRegistry) SaveEnvFile() (err error) {
|
|
// Now create the .env with all configured VendorsController info
|
|
var envFileContent bytes.Buffer
|
|
|
|
o.Defaults.Settings.FillEnvFileContent(&envFileContent)
|
|
o.PatternsLoader.SetupFillEnvFileContent(&envFileContent)
|
|
o.CustomPatterns.SetupFillEnvFileContent(&envFileContent)
|
|
o.Strategies.SetupFillEnvFileContent(&envFileContent)
|
|
|
|
for _, vendor := range o.VendorManager.Vendors {
|
|
vendor.SetupFillEnvFileContent(&envFileContent)
|
|
}
|
|
|
|
o.YouTube.SetupFillEnvFileContent(&envFileContent)
|
|
o.Jina.SetupFillEnvFileContent(&envFileContent)
|
|
o.Language.SetupFillEnvFileContent(&envFileContent)
|
|
|
|
err = o.Db.SaveEnv(envFileContent.String())
|
|
return
|
|
}
|
|
|
|
func (o *PluginRegistry) Setup() (err error) {
|
|
// Check if this is a first-time setup
|
|
isFirstRun := o.isFirstTimeSetup()
|
|
|
|
if isFirstRun {
|
|
err = o.runFirstTimeSetup()
|
|
} else {
|
|
err = o.runInteractiveSetup()
|
|
}
|
|
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Validate setup after completion
|
|
o.validateSetup()
|
|
|
|
return
|
|
}
|
|
|
|
// isFirstTimeSetup checks if this is a first-time setup
|
|
func (o *PluginRegistry) isFirstTimeSetup() bool {
|
|
// Check if patterns and strategies are not configured
|
|
patternsConfigured := o.PatternsLoader.IsConfigured()
|
|
strategiesConfigured := o.Strategies.IsConfigured()
|
|
hasVendor := len(o.VendorManager.Vendors) > 0
|
|
|
|
return !patternsConfigured || !strategiesConfigured || !hasVendor
|
|
}
|
|
|
|
// runFirstTimeSetup handles first-time setup with automatic pattern/strategy download
|
|
func (o *PluginRegistry) runFirstTimeSetup() (err error) {
|
|
fmt.Println("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
|
fmt.Println(i18n.T("setup_welcome_header"))
|
|
fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
|
|
|
// Step 1: Download patterns (required, automatic)
|
|
if !o.PatternsLoader.IsConfigured() {
|
|
fmt.Printf("\n%s\n", i18n.T("setup_step_downloading_patterns"))
|
|
if err = o.PatternsLoader.Setup(); err != nil {
|
|
return fmt.Errorf(i18n.T("setup_failed_download_patterns"), err)
|
|
}
|
|
if err = o.SaveEnvFile(); err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
// Step 2: Download strategies (required, automatic)
|
|
if !o.Strategies.IsConfigured() {
|
|
fmt.Printf("\n%s\n", i18n.T("setup_step_downloading_strategies"))
|
|
if err = o.Strategies.Setup(); err != nil {
|
|
return fmt.Errorf(i18n.T("setup_failed_download_strategies"), err)
|
|
}
|
|
if err = o.SaveEnvFile(); err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
// Step 3: Configure AI vendor (interactive)
|
|
if len(o.VendorManager.Vendors) == 0 {
|
|
fmt.Printf("\n%s\n", i18n.T("setup_step_configure_ai_provider"))
|
|
fmt.Printf(" %s\n", i18n.T("setup_ai_provider_required"))
|
|
fmt.Printf(" %s\n", i18n.T("setup_add_more_providers_later"))
|
|
fmt.Println()
|
|
|
|
if err = o.runVendorSetup(); err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
// Step 4: Set default vendor and model
|
|
if !o.Defaults.IsConfigured() {
|
|
fmt.Printf("\n%s\n", i18n.T("setup_step_setting_defaults"))
|
|
if err = o.Defaults.Setup(); err != nil {
|
|
return fmt.Errorf(i18n.T("setup_failed_set_defaults"), err)
|
|
}
|
|
if err = o.SaveEnvFile(); err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
fmt.Println("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
|
fmt.Println(i18n.T("setup_complete_header"))
|
|
fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
|
fmt.Printf("\n%s\n", i18n.T("setup_next_steps"))
|
|
fmt.Printf(" %s\n", i18n.T("setup_list_patterns"))
|
|
fmt.Printf(" %s\n", i18n.T("setup_try_pattern"))
|
|
fmt.Printf(" %s\n", i18n.T("setup_configure_more"))
|
|
fmt.Println()
|
|
|
|
return
|
|
}
|
|
|
|
// runVendorSetup helps user select and configure their first AI vendor
|
|
func (o *PluginRegistry) runVendorSetup() (err error) {
|
|
setupQuestion := plugins.NewSetupQuestion("Enter the number of the AI provider to configure")
|
|
groupsPlugins := util.NewGroupsItemsSelector(i18n.T("setup_available_ai_providers"),
|
|
func(plugin plugins.Plugin) string {
|
|
return plugin.GetSetupDescription()
|
|
})
|
|
|
|
groupsPlugins.AddGroupItems("", lo.Map(o.VendorsAll.Vendors,
|
|
func(vendor ai.Vendor, _ int) plugins.Plugin {
|
|
return vendor
|
|
})...)
|
|
|
|
groupsPlugins.Print(false)
|
|
|
|
if answerErr := setupQuestion.Ask(i18n.T("setup_enter_ai_provider_number")); answerErr != nil {
|
|
return answerErr
|
|
}
|
|
|
|
if setupQuestion.Value == "" {
|
|
return fmt.Errorf("%s", i18n.T("setup_no_ai_provider_selected"))
|
|
}
|
|
|
|
number, parseErr := strconv.Atoi(setupQuestion.Value)
|
|
if parseErr != nil {
|
|
return fmt.Errorf(i18n.T("setup_invalid_selection"), setupQuestion.Value)
|
|
}
|
|
|
|
var plugin plugins.Plugin
|
|
if _, plugin, err = groupsPlugins.GetGroupAndItemByItemNumber(number); err != nil {
|
|
return
|
|
}
|
|
|
|
if pluginSetupErr := plugin.Setup(); pluginSetupErr != nil {
|
|
return pluginSetupErr
|
|
}
|
|
|
|
if err = o.SaveEnvFile(); err != nil {
|
|
return
|
|
}
|
|
|
|
if o.VendorManager.FindByName(plugin.GetName()) == nil {
|
|
if vendor, ok := plugin.(ai.Vendor); ok {
|
|
o.VendorManager.AddVendors(vendor)
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// runInteractiveSetup runs the standard interactive setup menu
|
|
func (o *PluginRegistry) runInteractiveSetup() (err error) {
|
|
setupQuestion := plugins.NewSetupQuestion(i18n.T("setup_plugin_prompt"))
|
|
groupsPlugins := util.NewGroupsItemsSelector(i18n.T("setup_available_plugins"),
|
|
func(plugin plugins.Plugin) string {
|
|
var configuredLabel string
|
|
if plugin.IsConfigured() {
|
|
configuredLabel = i18n.T("plugin_configured")
|
|
} else {
|
|
configuredLabel = i18n.T("plugin_not_configured")
|
|
}
|
|
return fmt.Sprintf("%v%v", plugin.GetSetupDescription(), configuredLabel)
|
|
})
|
|
|
|
// Add vendors first under REQUIRED section
|
|
groupsPlugins.AddGroupItems(i18n.T("setup_required_configuration_header"), lo.Map(o.VendorsAll.Vendors,
|
|
func(vendor ai.Vendor, _ int) plugins.Plugin {
|
|
return vendor
|
|
})...)
|
|
|
|
// Add required tools
|
|
groupsPlugins.AddGroupItems(i18n.T("setup_required_tools"), o.Defaults, o.PatternsLoader, o.Strategies)
|
|
|
|
// Add optional tools
|
|
groupsPlugins.AddGroupItems(i18n.T("setup_optional_configuration_header"), o.CustomPatterns, o.Jina, o.Language, o.YouTube)
|
|
|
|
for {
|
|
groupsPlugins.Print(false)
|
|
|
|
if answerErr := setupQuestion.Ask(i18n.T("setup_plugin_number")); answerErr != nil {
|
|
break
|
|
}
|
|
|
|
if setupQuestion.Value == "" {
|
|
break
|
|
}
|
|
number, parseErr := strconv.Atoi(setupQuestion.Value)
|
|
setupQuestion.Value = ""
|
|
|
|
if parseErr == nil {
|
|
var plugin plugins.Plugin
|
|
if _, plugin, err = groupsPlugins.GetGroupAndItemByItemNumber(number); err != nil {
|
|
return
|
|
}
|
|
|
|
if pluginSetupErr := plugin.Setup(); pluginSetupErr != nil {
|
|
println(pluginSetupErr.Error())
|
|
} else {
|
|
if err = o.SaveEnvFile(); err != nil {
|
|
break
|
|
}
|
|
}
|
|
|
|
if o.VendorManager.FindByName(plugin.GetName()) == nil {
|
|
if vendor, ok := plugin.(ai.Vendor); ok {
|
|
o.VendorManager.AddVendors(vendor)
|
|
}
|
|
}
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
|
|
err = o.SaveEnvFile()
|
|
|
|
return
|
|
}
|
|
|
|
// validateSetup checks if required components are configured and warns user
|
|
func (o *PluginRegistry) validateSetup() {
|
|
fmt.Println("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
|
fmt.Println(i18n.T("setup_validation_header"))
|
|
fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
|
|
|
missingRequired := false
|
|
|
|
// Check AI vendor
|
|
if len(o.VendorManager.Vendors) > 0 {
|
|
fmt.Printf(" %s\n", i18n.T("setup_validation_ai_provider_configured"))
|
|
} else {
|
|
fmt.Printf(" %s\n", i18n.T("setup_validation_ai_provider_missing"))
|
|
missingRequired = true
|
|
}
|
|
|
|
// Check default model
|
|
if o.Defaults.IsConfigured() {
|
|
fmt.Printf(" %s\n", fmt.Sprintf(i18n.T("setup_validation_defaults_configured"), o.Defaults.Vendor.Value, o.Defaults.Model.Value))
|
|
} else {
|
|
fmt.Printf(" %s\n", i18n.T("setup_validation_defaults_missing"))
|
|
missingRequired = true
|
|
}
|
|
|
|
// Check patterns
|
|
if o.PatternsLoader.IsConfigured() {
|
|
fmt.Printf(" %s\n", i18n.T("setup_validation_patterns_configured"))
|
|
} else {
|
|
fmt.Printf(" %s\n", i18n.T("setup_validation_patterns_missing"))
|
|
missingRequired = true
|
|
}
|
|
|
|
// Check strategies
|
|
if o.Strategies.IsConfigured() {
|
|
fmt.Printf(" %s\n", i18n.T("setup_validation_strategies_configured"))
|
|
} else {
|
|
fmt.Printf(" %s\n", i18n.T("setup_validation_strategies_missing"))
|
|
missingRequired = true
|
|
}
|
|
|
|
fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
|
|
|
if missingRequired {
|
|
fmt.Printf("\n%s\n", i18n.T("setup_validation_incomplete_warning"))
|
|
fmt.Printf(" %s\n", i18n.T("setup_validation_incomplete_help"))
|
|
fmt.Println()
|
|
} else {
|
|
fmt.Printf("\n%s\n", i18n.T("setup_validation_complete"))
|
|
fmt.Println()
|
|
}
|
|
}
|
|
|
|
func (o *PluginRegistry) SetupVendor(vendorName string) (err error) {
|
|
if err = o.VendorsAll.SetupVendor(vendorName, o.VendorManager.VendorsByName); err != nil {
|
|
return
|
|
}
|
|
err = o.SaveEnvFile()
|
|
return
|
|
}
|
|
|
|
func (o *PluginRegistry) ConfigureVendors() {
|
|
o.VendorManager.Clear()
|
|
for _, vendor := range o.VendorsAll.Vendors {
|
|
if vendorErr := vendor.Configure(); vendorErr == nil && vendor.IsConfigured() {
|
|
o.VendorManager.AddVendors(vendor)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (o *PluginRegistry) GetModels() (ret *ai.VendorsModels, err error) {
|
|
o.ConfigureVendors()
|
|
ret, err = o.VendorManager.GetModels()
|
|
return
|
|
}
|
|
|
|
// Configure buildClient VendorsController based on the environment variables
|
|
func (o *PluginRegistry) Configure() (err error) {
|
|
o.ConfigureVendors()
|
|
_ = o.Defaults.Configure()
|
|
if err := o.CustomPatterns.Configure(); err != nil {
|
|
return fmt.Errorf("error configuring CustomPatterns: %w", err)
|
|
}
|
|
_ = o.PatternsLoader.Configure()
|
|
|
|
// Refresh the database custom patterns directory after custom patterns plugin is configured
|
|
customPatternsDir := os.Getenv("CUSTOM_PATTERNS_DIRECTORY")
|
|
if customPatternsDir != "" {
|
|
// Expand home directory if needed
|
|
if strings.HasPrefix(customPatternsDir, "~/") {
|
|
if homeDir, err := os.UserHomeDir(); err == nil {
|
|
customPatternsDir = filepath.Join(homeDir, customPatternsDir[2:])
|
|
}
|
|
}
|
|
o.Db.Patterns.CustomPatternsDir = customPatternsDir
|
|
o.PatternsLoader.Patterns.CustomPatternsDir = customPatternsDir
|
|
}
|
|
|
|
//YouTube and Jina are not mandatory, so ignore not configured error
|
|
_ = o.YouTube.Configure()
|
|
_ = o.Jina.Configure()
|
|
_ = o.Language.Configure()
|
|
return
|
|
}
|
|
|
|
func (o *PluginRegistry) GetChatter(model string, modelContextLength int, vendorName string, strategy string, stream bool, dryRun bool) (ret *Chatter, err error) {
|
|
ret = &Chatter{
|
|
db: o.Db,
|
|
Stream: stream,
|
|
DryRun: dryRun,
|
|
}
|
|
|
|
defaultModel := o.Defaults.Model.Value
|
|
defaultModelContextLength, err := strconv.Atoi(o.Defaults.ModelContextLength.Value)
|
|
defaultVendor := o.Defaults.Vendor.Value
|
|
vendorManager := o.VendorManager
|
|
|
|
if err != nil {
|
|
defaultModelContextLength = 0
|
|
err = nil
|
|
}
|
|
|
|
ret.modelContextLength = modelContextLength
|
|
if ret.modelContextLength == 0 {
|
|
ret.modelContextLength = defaultModelContextLength
|
|
}
|
|
|
|
if dryRun {
|
|
ret.vendor = dryrun.NewClient()
|
|
ret.model = model
|
|
if ret.model == "" {
|
|
ret.model = defaultModel
|
|
}
|
|
} else if model == "" {
|
|
if vendorName != "" {
|
|
ret.vendor = vendorManager.FindByName(vendorName)
|
|
} else {
|
|
ret.vendor = vendorManager.FindByName(defaultVendor)
|
|
}
|
|
ret.model = defaultModel
|
|
} else {
|
|
var models *ai.VendorsModels
|
|
if models, err = vendorManager.GetModels(); err != nil {
|
|
return
|
|
}
|
|
|
|
// Normalize model name to match actual available model (case-insensitive)
|
|
// This must be done BEFORE checking vendor availability
|
|
actualModelName := models.FindModelNameCaseInsensitive(model)
|
|
if actualModelName != "" {
|
|
model = actualModelName // Use normalized name for all subsequent checks
|
|
}
|
|
|
|
if vendorName != "" {
|
|
// ensure vendor exists and provides model
|
|
ret.vendor = vendorManager.FindByName(vendorName)
|
|
availableVendors := models.FindGroupsByItem(model)
|
|
vendorAvailable := lo.ContainsBy(availableVendors, func(name string) bool {
|
|
return strings.EqualFold(name, vendorName)
|
|
})
|
|
if ret.vendor == nil || !vendorAvailable {
|
|
err = fmt.Errorf("model %s not available for vendor %s", model, vendorName)
|
|
return
|
|
}
|
|
} else {
|
|
availableVendors := models.FindGroupsByItem(model)
|
|
if len(availableVendors) > 1 {
|
|
debuglog.Log("Warning: multiple vendors provide model %s: %s. Using %s. Specify --vendor to select a vendor.\n", model, strings.Join(availableVendors, ", "), availableVendors[0])
|
|
}
|
|
ret.vendor = vendorManager.FindByName(models.FindGroupsByItemFirst(model))
|
|
}
|
|
|
|
ret.model = model
|
|
}
|
|
|
|
if ret.vendor == nil {
|
|
var errMsg string
|
|
if defaultModel == "" || defaultVendor == "" {
|
|
errMsg = "Please run, fabric --setup, and select default model and vendor."
|
|
} else {
|
|
errMsg = "could not find vendor."
|
|
}
|
|
err = fmt.Errorf(
|
|
" Requested Model = %s\n Default Model = %s\n Default Vendor = %s.\n\n%s",
|
|
model, defaultModel, defaultVendor, errMsg)
|
|
return
|
|
}
|
|
ret.strategy = strategy
|
|
return
|
|
}
|