202 lines
4.8 KiB
Go
202 lines
4.8 KiB
Go
package launch
|
|
|
|
import (
|
|
"context"
|
|
"slices"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/ollama/ollama/api"
|
|
modelpkg "github.com/ollama/ollama/types/model"
|
|
)
|
|
|
|
// LaunchModel is the model metadata Launch passes to integration config
|
|
// writers after resolving selected model names through the per-run inventory.
|
|
type LaunchModel struct {
|
|
Name string
|
|
Remote bool
|
|
ToolCapable bool
|
|
Capabilities []modelpkg.Capability
|
|
ContextLength int
|
|
MaxOutputTokens int
|
|
EmbeddingLength int
|
|
Size int64
|
|
Details api.ModelDetails
|
|
}
|
|
|
|
type modelInfo = LaunchModel
|
|
|
|
// ModelInfo re-exports launcher model inventory details for callers.
|
|
type ModelInfo = LaunchModel
|
|
|
|
func (m LaunchModel) HasCapability(capability modelpkg.Capability) bool {
|
|
return slices.Contains(m.Capabilities, capability)
|
|
}
|
|
|
|
func (m LaunchModel) WithCloudLimits() LaunchModel {
|
|
if limit, ok := lookupCloudModelLimit(m.Name); ok {
|
|
if m.ContextLength <= 0 {
|
|
m.ContextLength = limit.Context
|
|
}
|
|
if m.MaxOutputTokens <= 0 {
|
|
m.MaxOutputTokens = limit.Output
|
|
}
|
|
}
|
|
return m
|
|
}
|
|
|
|
type modelInventory struct {
|
|
client *api.Client
|
|
|
|
mu sync.Mutex
|
|
loaded bool
|
|
models []LaunchModel
|
|
err error
|
|
}
|
|
|
|
func newModelInventory(client *api.Client) *modelInventory {
|
|
return &modelInventory{client: client}
|
|
}
|
|
|
|
func (i *modelInventory) Load(ctx context.Context) ([]LaunchModel, error) {
|
|
return i.load(ctx, false)
|
|
}
|
|
|
|
func (i *modelInventory) Refresh(ctx context.Context) ([]LaunchModel, error) {
|
|
return i.load(ctx, true)
|
|
}
|
|
|
|
func (i *modelInventory) load(ctx context.Context, force bool) ([]LaunchModel, error) {
|
|
if i == nil || i.client == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
i.mu.Lock()
|
|
defer i.mu.Unlock()
|
|
|
|
if i.loaded && !force {
|
|
return cloneLaunchModels(i.models), i.err
|
|
}
|
|
|
|
resp, err := i.client.List(ctx)
|
|
if err != nil {
|
|
i.models = nil
|
|
i.err = err
|
|
i.loaded = true
|
|
return nil, err
|
|
}
|
|
|
|
i.models = make([]LaunchModel, 0, len(resp.Models))
|
|
for _, model := range resp.Models {
|
|
i.models = append(i.models, launchModelFromListResponse(model))
|
|
}
|
|
i.err = nil
|
|
i.loaded = true
|
|
|
|
return cloneLaunchModels(i.models), i.err
|
|
}
|
|
|
|
func (i *modelInventory) Resolve(ctx context.Context, names []string) []LaunchModel {
|
|
names = dedupeModelList(names)
|
|
if len(names) == 0 {
|
|
return nil
|
|
}
|
|
|
|
models, err := i.Load(ctx)
|
|
if err != nil {
|
|
models = nil
|
|
}
|
|
|
|
resolved, localMiss := resolveLaunchModels(names, models)
|
|
if localMiss {
|
|
if refreshed, err := i.Refresh(ctx); err == nil {
|
|
resolved, _ = resolveLaunchModels(names, refreshed)
|
|
}
|
|
}
|
|
return resolved
|
|
}
|
|
|
|
func resolveLaunchModels(names []string, models []LaunchModel) ([]LaunchModel, bool) {
|
|
resolved := make([]LaunchModel, 0, len(names))
|
|
localMiss := false
|
|
for _, name := range names {
|
|
if model, ok := findLaunchModel(models, name); ok {
|
|
resolved = append(resolved, model.WithCloudLimits())
|
|
continue
|
|
}
|
|
if !isCloudModelName(name) {
|
|
localMiss = true
|
|
}
|
|
resolved = append(resolved, fallbackLaunchModel(name))
|
|
}
|
|
return resolved, localMiss
|
|
}
|
|
|
|
func launchModelFromListResponse(model api.ListModelResponse) LaunchModel {
|
|
return LaunchModel{
|
|
Name: model.Name,
|
|
Remote: model.RemoteModel != "",
|
|
ToolCapable: slices.Contains(model.Capabilities, modelpkg.CapabilityTools),
|
|
Capabilities: append([]modelpkg.Capability(nil), model.Capabilities...),
|
|
ContextLength: model.Details.ContextLength,
|
|
EmbeddingLength: model.Details.EmbeddingLength,
|
|
Size: model.Size,
|
|
Details: model.Details,
|
|
}.WithCloudLimits()
|
|
}
|
|
|
|
func fallbackLaunchModel(name string) LaunchModel {
|
|
return LaunchModel{Name: name, Remote: isCloudModelName(name)}.WithCloudLimits()
|
|
}
|
|
|
|
func findLaunchModel(models []LaunchModel, name string) (LaunchModel, bool) {
|
|
for _, model := range models {
|
|
if launchModelMatches(model.Name, name) {
|
|
return cloneLaunchModel(model), true
|
|
}
|
|
}
|
|
return LaunchModel{}, false
|
|
}
|
|
|
|
func launchModelMatches(candidate, name string) bool {
|
|
if candidate == name {
|
|
return true
|
|
}
|
|
return strings.TrimSuffix(candidate, ":latest") == name
|
|
}
|
|
|
|
func cloneLaunchModel(model LaunchModel) LaunchModel {
|
|
model.Capabilities = append([]modelpkg.Capability(nil), model.Capabilities...)
|
|
model.Details.Families = append([]string(nil), model.Details.Families...)
|
|
return model
|
|
}
|
|
|
|
func cloneLaunchModels(models []LaunchModel) []LaunchModel {
|
|
cloned := make([]LaunchModel, len(models))
|
|
for i, model := range models {
|
|
cloned[i] = cloneLaunchModel(model)
|
|
}
|
|
return cloned
|
|
}
|
|
|
|
func launchModelNames(models []LaunchModel) []string {
|
|
names := make([]string, 0, len(models))
|
|
for _, model := range models {
|
|
if model.Name != "" {
|
|
names = append(names, model.Name)
|
|
}
|
|
}
|
|
return names
|
|
}
|
|
|
|
func launchModelsFromNames(names []string) []LaunchModel {
|
|
models := make([]LaunchModel, 0, len(names))
|
|
for _, name := range names {
|
|
if name == "" {
|
|
continue
|
|
}
|
|
models = append(models, fallbackLaunchModel(name))
|
|
}
|
|
return models
|
|
}
|