Skip to content

Commit 6339f7b

Browse files
authored
Added ollama (#69)
* Added ollama * Fixed panic * Updates after PR comments
1 parent 215814c commit 6339f7b

27 files changed

Lines changed: 1274 additions & 195 deletions

cmd/llm/mcp.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ func (cmd *HeartbeatMCPCommand) Run(ctx server.Cmd) error {
8787
}
8888

8989
// Mount the MCP handler on the router at the HTTP prefix
90-
cmd.Register(func(router *httprouter.Router, _ server.Cmd) error {
90+
cmd.Register(func(router *httprouter.Router) error {
9191
return router.RegisterFunc("", srv.Handler().ServeHTTP, false, nil)
9292
})
9393

cmd/llm/model.go

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"fmt"
5+
"strings"
56

67
// Packages
78
otel "github.com/mutablelogic/go-client/pkg/otel"
@@ -18,9 +19,11 @@ import (
1819
// TYPES
1920

2021
type ModelCommands struct {
21-
Providers ProvidersCommand `cmd:"" name:"providers" help:"List providers." group:"MODEL"`
22-
ListModels ListModelsCommand `cmd:"" name:"models" help:"List models." group:"MODEL"`
23-
GetModel GetModelCommand `cmd:"" name:"model" help:"Get model." group:"MODEL"`
22+
Providers ProvidersCommand `cmd:"" name:"providers" help:"List providers." group:"MODEL"`
23+
ListModels ListModelsCommand `cmd:"" name:"models" help:"List models." group:"MODEL"`
24+
GetModel GetModelCommand `cmd:"" name:"model" help:"Get model." group:"MODEL"`
25+
DownloadModel DownloadModelCommand `cmd:"" name:"download" help:"Download a model." group:"MODEL"`
26+
DeleteModel DeleteModelCommand `cmd:"" name:"delete-model" help:"Delete a model." group:"MODEL"`
2427
}
2528

2629
type ProvidersCommand struct{}
@@ -37,6 +40,17 @@ type GetModelCommand struct {
3740
Default bool `name:"default" help:"Save as default model" optional:""`
3841
}
3942

43+
type DownloadModelCommand struct {
44+
Name string `arg:"" name:"name" help:"Model name to download"`
45+
Provider string `name:"provider" help:"Provider name" optional:""`
46+
Progress bool `name:"progress" help:"Show download progress" default:"true" negatable:""`
47+
}
48+
49+
type DeleteModelCommand struct {
50+
Name string `arg:"" name:"name" help:"Model name to delete"`
51+
Provider string `name:"provider" help:"Provider name" optional:""`
52+
}
53+
4054
///////////////////////////////////////////////////////////////////////////////
4155
// COMMANDS
4256

@@ -169,3 +183,81 @@ func (cmd *GetModelCommand) Run(ctx server.Cmd) (err error) {
169183
// Return success
170184
return nil
171185
}
186+
187+
func (cmd *DownloadModelCommand) Run(ctx server.Cmd) (err error) {
188+
client, err := clientFor(ctx)
189+
if err != nil {
190+
return err
191+
}
192+
193+
// OTEL
194+
parent, endSpan := otel.StartSpan(ctx.Tracer(), ctx.Context(), "DownloadModelCommand",
195+
attribute.String("request", types.Stringify(cmd)),
196+
)
197+
defer func() { endSpan(err) }()
198+
199+
// Build options
200+
opts := []opt.Opt{}
201+
if cmd.Provider != "" {
202+
opts = append(opts, httpclient.WithProvider(cmd.Provider))
203+
}
204+
const barWidth = 20
205+
if cmd.Progress {
206+
opts = append(opts, opt.WithProgress(func(status string, percent float64) {
207+
if percent > 0 {
208+
filled := int(percent / 100.0 * barWidth)
209+
if filled > barWidth {
210+
filled = barWidth
211+
}
212+
bar := strings.Repeat("█", filled) + strings.Repeat("░", barWidth-filled)
213+
fmt.Printf("\r %-30s [%s] %5.1f%%", status, bar, percent)
214+
} else {
215+
fmt.Printf("\r %-52s", status)
216+
}
217+
}))
218+
}
219+
220+
// Download model
221+
model, err := client.DownloadModel(parent, cmd.Name, opts...)
222+
if cmd.Progress {
223+
fmt.Println() // newline after progress output
224+
}
225+
if err != nil {
226+
return err
227+
}
228+
229+
// Print
230+
if ctx.IsDebug() {
231+
fmt.Println(model)
232+
} else {
233+
fmt.Printf("Downloaded model: %s\n", model.Name)
234+
}
235+
return nil
236+
}
237+
238+
func (cmd *DeleteModelCommand) Run(ctx server.Cmd) (err error) {
239+
client, err := clientFor(ctx)
240+
if err != nil {
241+
return err
242+
}
243+
244+
// OTEL
245+
parent, endSpan := otel.StartSpan(ctx.Tracer(), ctx.Context(), "DeleteModelCommand",
246+
attribute.String("request", types.Stringify(cmd)),
247+
)
248+
defer func() { endSpan(err) }()
249+
250+
// Build options
251+
opts := []opt.Opt{}
252+
if cmd.Provider != "" {
253+
opts = append(opts, httpclient.WithProvider(cmd.Provider))
254+
}
255+
256+
// Delete model
257+
if err := client.DeleteModel(parent, cmd.Name, opts...); err != nil {
258+
return err
259+
}
260+
261+
fmt.Printf("Deleted model: %s\n", cmd.Name)
262+
return nil
263+
}

cmd/llm/server.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
eliza "github.com/mutablelogic/go-llm/pkg/provider/eliza"
2121
google "github.com/mutablelogic/go-llm/pkg/provider/google"
2222
mistral "github.com/mutablelogic/go-llm/pkg/provider/mistral"
23+
ollama "github.com/mutablelogic/go-llm/pkg/provider/ollama"
2324
schema "github.com/mutablelogic/go-llm/pkg/schema"
2425
session "github.com/mutablelogic/go-llm/pkg/store"
2526
weatherapi "github.com/mutablelogic/go-llm/pkg/weatherapi"
@@ -39,6 +40,7 @@ type RunServer struct {
3940
GeminiAPIKey string `name:"gemini-api-key" env:"GEMINI_API_KEY" help:"Google Gemini API key"`
4041
AnthropicAPIKey string `name:"anthropic-api-key" env:"ANTHROPIC_API_KEY" help:"Anthropic API key"`
4142
MistralAPIKey string `name:"mistral-api-key" env:"MISTRAL_API_KEY" help:"Mistral API key"`
43+
OllamaURL string `name:"ollama-url" env:"OLLAMA_URL" help:"Ollama endpoint URL (e.g. http://localhost:11434/api)"`
4244
Eliza bool `name:"eliza" help:"Include ELIZA provider (no API key required)"`
4345

4446
// Tool API Keys
@@ -56,7 +58,7 @@ type RunServer struct {
5658

5759
func (s *RunServer) Run(ctx server.Cmd) error {
5860
return s.WithManager(ctx, func(mgr *manager.Manager, v string) error {
59-
s.RunServer.Register(func(router *httprouter.Router, c server.Cmd) error {
61+
s.RunServer.Register(func(router *httprouter.Router) error {
6062
return httphandler.RegisterHandlers(mgr, router, true)
6163
})
6264
return s.RunServer.Run(ctx)
@@ -78,6 +80,7 @@ func (cmd *RunServer) WithManager(ctx server.Cmd, fn func(*manager.Manager, stri
7880
cmd.AnthropicClient,
7981
cmd.GeminiClient,
8082
cmd.MistralClient,
83+
cmd.OllamaClient,
8184
cmd.ElizaClient,
8285
} {
8386
if o, err := fn(clientOpts...); err != nil {
@@ -89,7 +92,7 @@ func (cmd *RunServer) WithManager(ctx server.Cmd, fn func(*manager.Manager, stri
8992

9093
// Check if at least one client is configured
9194
if len(opts) == 0 {
92-
return fmt.Errorf("no API keys configured. Set --gemini-api-key, --anthropic-api-key, or --mistral-api-key (or use environment variables)")
95+
return fmt.Errorf("no providers configured. Set --gemini-api-key, --anthropic-api-key, --mistral-api-key, --ollama-url (or use environment variables)")
9396
}
9497

9598
// Add a session store
@@ -261,6 +264,14 @@ func (cmd *RunServer) MistralClient(opts ...goclient.ClientOpt) ([]manager.Opt,
261264
return []manager.Opt{manager.WithClient(c)}, err
262265
}
263266

267+
func (cmd *RunServer) OllamaClient(opts ...goclient.ClientOpt) ([]manager.Opt, error) {
268+
if cmd.OllamaURL == "" {
269+
return nil, nil
270+
}
271+
c, err := ollama.New(cmd.OllamaURL, opts...)
272+
return []manager.Opt{manager.WithClient(c)}, err
273+
}
274+
264275
func (cmd *RunServer) ElizaClient(opts ...goclient.ClientOpt) ([]manager.Opt, error) {
265276
if !cmd.Eliza {
266277
return nil, nil

go.mod

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,66 @@ require (
1111
github.com/google/jsonschema-go v0.4.2
1212
github.com/google/uuid v1.6.0
1313
github.com/igor-pavlenko/goldmark-telegram v0.2.0
14-
github.com/modelcontextprotocol/go-sdk v1.4.0
14+
github.com/modelcontextprotocol/go-sdk v1.4.1
1515
github.com/muesli/reflow v0.3.0
1616
github.com/muesli/termenv v0.16.0
1717
github.com/mutablelogic/go-client v1.4.3
18-
github.com/mutablelogic/go-server v1.6.16
18+
github.com/mutablelogic/go-server v1.6.17
1919
github.com/stretchr/testify v1.11.1
2020
github.com/yuin/goldmark v1.7.16
2121
go.opentelemetry.io/otel v1.42.0
2222
go.opentelemetry.io/otel/trace v1.42.0
23-
golang.org/x/crypto v0.48.0
23+
golang.org/x/crypto v0.49.0
2424
golang.org/x/oauth2 v0.36.0
2525
golang.org/x/sync v0.20.0
26-
golang.org/x/term v0.40.0
26+
golang.org/x/term v0.41.0
2727
gopkg.in/telebot.v4 v4.0.0-beta.7
2828
gopkg.in/yaml.v3 v3.0.1
2929
)
3030

3131
require (
32+
dario.cat/mergo v1.0.2 // indirect
33+
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
34+
github.com/Microsoft/go-winio v0.6.2 // indirect
35+
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
36+
github.com/containerd/errdefs v1.0.0 // indirect
37+
github.com/containerd/errdefs/pkg v0.3.0 // indirect
38+
github.com/containerd/log v0.1.0 // indirect
39+
github.com/containerd/platforms v0.2.1 // indirect
40+
github.com/cpuguy83/dockercfg v0.3.2 // indirect
41+
github.com/distribution/reference v0.6.0 // indirect
42+
github.com/docker/docker v28.5.2+incompatible // indirect
43+
github.com/docker/go-connections v0.6.0 // indirect
44+
github.com/docker/go-units v0.5.0 // indirect
45+
github.com/ebitengine/purego v0.10.0 // indirect
46+
github.com/felixge/httpsnoop v1.0.4 // indirect
47+
github.com/go-ole/go-ole v1.3.0 // indirect
3248
github.com/jackc/pgpassfile v1.0.0 // indirect
3349
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
3450
github.com/jackc/pgx/v5 v5.8.0 // indirect
3551
github.com/jackc/puddle/v2 v2.2.2 // indirect
52+
github.com/klauspost/compress v1.18.4 // indirect
53+
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 // indirect
54+
github.com/magiconair/properties v1.8.10 // indirect
55+
github.com/moby/docker-image-spec v1.3.1 // indirect
56+
github.com/moby/go-archive v0.2.0 // indirect
57+
github.com/moby/patternmatcher v0.6.0 // indirect
58+
github.com/moby/sys/sequential v0.6.0 // indirect
59+
github.com/moby/sys/user v0.4.0 // indirect
60+
github.com/moby/sys/userns v0.1.0 // indirect
61+
github.com/moby/term v0.5.2 // indirect
62+
github.com/morikuni/aec v1.1.0 // indirect
63+
github.com/opencontainers/go-digest v1.0.0 // indirect
64+
github.com/opencontainers/image-spec v1.1.1 // indirect
65+
github.com/pkg/errors v0.9.1 // indirect
66+
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
67+
github.com/shirou/gopsutil/v4 v4.26.2 // indirect
68+
github.com/sirupsen/logrus v1.9.4 // indirect
69+
github.com/testcontainers/testcontainers-go v0.41.0 // indirect
70+
github.com/tklauser/go-sysconf v0.3.16 // indirect
71+
github.com/tklauser/numcpus v0.11.0 // indirect
72+
github.com/yusufpapurcu/wmi v1.2.4 // indirect
73+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
3674
)
3775

3876
require (
@@ -45,7 +83,7 @@ require (
4583
github.com/charmbracelet/colorprofile v0.4.3 // indirect
4684
github.com/charmbracelet/x/ansi v0.11.6 // indirect
4785
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
48-
github.com/charmbracelet/x/exp/slice v0.0.0-20260309091332-e8ca31595cc4 // indirect
86+
github.com/charmbracelet/x/exp/slice v0.0.0-20260311145557-c83711a11ffa // indirect
4987
github.com/charmbracelet/x/term v0.2.2 // indirect
5088
github.com/clipperhouse/displaywidth v0.11.0 // indirect
5189
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
@@ -63,11 +101,11 @@ require (
63101
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
64102
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
65103
github.com/muesli/cancelreader v0.2.2 // indirect
66-
github.com/mutablelogic/go-pg v1.1.2
104+
github.com/mutablelogic/go-pg v1.1.3
67105
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
68106
github.com/rivo/uniseg v0.4.7 // indirect
69107
github.com/segmentio/asm v1.2.1 // indirect
70-
github.com/segmentio/encoding v0.5.3 // indirect
108+
github.com/segmentio/encoding v0.5.4 // indirect
71109
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
72110
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
73111
github.com/yuin/goldmark-emoji v1.0.6 // indirect
@@ -76,13 +114,13 @@ require (
76114
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 // indirect
77115
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 // indirect
78116
go.opentelemetry.io/otel/metric v1.42.0 // indirect
79-
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
117+
go.opentelemetry.io/otel/sdk v1.42.0
80118
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
81-
golang.org/x/net v0.51.0 // indirect
119+
golang.org/x/net v0.52.0 // indirect
82120
golang.org/x/sys v0.42.0 // indirect
83-
golang.org/x/text v0.34.0 // indirect
84-
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 // indirect
85-
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
121+
golang.org/x/text v0.35.0
122+
google.golang.org/genproto/googleapis/api v0.0.0-20260311181403-84a4fc48630c // indirect
123+
google.golang.org/genproto/googleapis/rpc v0.0.0-20260311181403-84a4fc48630c // indirect
86124
google.golang.org/grpc v1.79.2 // indirect
87125
google.golang.org/protobuf v1.36.11 // indirect
88126
)

0 commit comments

Comments
 (0)