| 💾 Saved. d722711 k33g 7h ago | 1 | package engine |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "fmt" |
| 6 | "sort" |
| 7 | "strings" |
| 8 | |
| 9 | "mm/internal/config" |
| 10 | |
| 11 | "github.com/firebase/genkit/go/genkit" |
| 12 | ) |
| 13 | |
| 14 | // Provider is what changes from one LLM server to the next. There is ONE |
| 15 | // implementation per wire protocol, not one per vendor: Docker Model Runner and |
| 16 | // llama-server both speak the OpenAI chat-completions format, and differ only |
| 17 | // in a base URL, a key policy and the words of their error messages. Adding a |
| 18 | // server that speaks the same protocol is a registry entry (see openai_compat.go); |
| 19 | // adding a protocol (Anthropic's Messages API, Ollama's /api/chat) is a new type. |
| 20 | type Provider interface { |
| 21 | // Name is the registry key AND the Genkit plugin name, hence the prefix of |
| 22 | // the model reference Generate uses ("dmr/<id>"). |
| 23 | Name() string |
| 24 | |
| 25 | // Resolve turns the loaded config into a concrete Backend: the URL after |
| 26 | // environment overrides and the fallback probe, the key read from the |
| 27 | // environment, the model in the provider's own naming. |
| 28 | Resolve(cfg config.Config) (Backend, error) |
| 29 | |
| 30 | // Open initialises Genkit with this provider's plugin, declares the model |
| 31 | // (stating that it knows how to call tools) and returns the full model |
| 32 | // reference. One Genkit per Engine for now; several backends in one Genkit |
| 33 | // is the seam for a `/model` command later. |
| 34 | Open(ctx context.Context, b Backend) (*genkit.Genkit, string, error) |
| 35 | |
| 36 | // Probe learns what it can about the server before the first question. |
| 37 | // Best effort, never fatal: a server started AFTER the agent is a normal |
| 38 | // demo situation, not an error. |
| 39 | Probe(ctx context.Context, b Backend) Info |
| 40 | |
| 41 | // Explain rewrites a transport error as ONE actionable line, in the words of |
| 42 | // this server: "start llama-server with --jinja" means nothing to a DMR |
| 43 | // user, and "docker model pull" nothing to a llama.cpp user. |
| 44 | Explain(err error, b Backend) string |
| 45 | } |
| 46 | |
| 47 | // Backend is a resolved target: everything Open and Explain need, and nothing |
| 48 | // that still depends on the environment. |
| 49 | type Backend struct { |
| 50 | Provider string |
| 51 | BaseURL string |
| 52 | Model string // in the provider's naming, no Genkit prefix |
| 53 | APIKey string // the resolved value; a placeholder when the server ignores it |
| 54 | // APIKeyEnv is the variable the key was read from — the yaml's `apiKeyEnv`, |
| 55 | // else the provider's default — or "" when neither names one. Explain cites |
| 56 | // it on a 401: the first version named a variable nothing read, and a |
| 57 | // message that sends the user to `export` the wrong name is worse than none. |
| 58 | APIKeyEnv string |
| 59 | // ContextWindow is the hint from the config, 0 when absent. The probe may |
| 60 | // fill it from the server (llama-server tells its n_ctx); the config wins, |
| 61 | // because the operator knows what they started the server with. |
| 62 | ContextWindow int |
| 63 | } |
| 64 | |
| 65 | // Info is what Probe found out. Warnings are printed one per line at start-up, |
| 66 | // dimmed like the config line; they are advice, not failures. |
| 67 | type Info struct { |
| 68 | Reachable bool |
| 69 | ContextWindow int // 0 = unknown |
| 70 | ContextSource string // "config", "/props", … — a number without an origin never gets corrected |
| 71 | Warnings []string |
| 72 | } |
| 73 | |
| 74 | // registry: the providers this build knows. Keys are the values `provider:` |
| 75 | // accepts in agent.yaml. |
| 76 | var registry = map[string]Provider{} |
| 77 | |
| 78 | func register(p Provider) { registry[p.Name()] = p } |
| 79 | |
| 80 | // Lookup finds a provider by its config name. The error lists the known names: |
| 81 | // the user typed one, the fix is to pick another. |
| 82 | func Lookup(name string) (Provider, error) { |
| 83 | if p, ok := registry[name]; ok { |
| 84 | return p, nil |
| 85 | } |
| 86 | return nil, fmt.Errorf("unknown provider %q (known: %s)", name, strings.Join(Names(), ", ")) |
| 87 | } |
| 88 | |
| 89 | // Names lists the registered providers, sorted for stable messages. |
| 90 | func Names() []string { |
| 91 | names := make([]string, 0, len(registry)) |
| 92 | for n := range registry { |
| 93 | names = append(names, n) |
| 94 | } |
| 95 | sort.Strings(names) |
| 96 | return names |
| 97 | } |