routes.go 15.2 KB
Newer Older
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1
2
3
package server

import (
4
	"context"
Michael Yang's avatar
Michael Yang committed
5
	"encoding/json"
6
	"errors"
7
	"fmt"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
8
	"io"
9
	"io/fs"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
10
11
12
	"log"
	"net"
	"net/http"
13
	"os"
14
	"os/signal"
Michael Yang's avatar
Michael Yang committed
15
	"path/filepath"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
16
	"reflect"
17
	"runtime"
Patrick Devine's avatar
Patrick Devine committed
18
	"strconv"
Michael Yang's avatar
Michael Yang committed
19
	"strings"
Michael Yang's avatar
Michael Yang committed
20
	"sync"
21
	"syscall"
22
	"time"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
23

Michael Yang's avatar
Michael Yang committed
24
	"github.com/gin-contrib/cors"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
25
26
	"github.com/gin-gonic/gin"

Jeffrey Morgan's avatar
Jeffrey Morgan committed
27
	"github.com/jmorganca/ollama/api"
28
	"github.com/jmorganca/ollama/llm"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
29
30
)

Michael Yang's avatar
Michael Yang committed
31
32
33
34
35
36
37
38
39
40
41
42
43
44
var mode string = gin.DebugMode

func init() {
	switch mode {
	case gin.DebugMode:
	case gin.ReleaseMode:
	case gin.TestMode:
	default:
		mode = gin.DebugMode
	}

	gin.SetMode(mode)
}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
45
var loaded struct {
Michael Yang's avatar
Michael Yang committed
46
47
	mu sync.Mutex

48
	llm llm.LLM
Michael Yang's avatar
Michael Yang committed
49
50
51

	expireAt    time.Time
	expireTimer *time.Timer
Jeffrey Morgan's avatar
Jeffrey Morgan committed
52

53
54
	digest  string
	options api.Options
Michael Yang's avatar
Michael Yang committed
55
56
}

57
58
var defaultSessionDuration = 5 * time.Minute

Bruce MacDonald's avatar
Bruce MacDonald committed
59
// load a model into memory if it is not already loaded, it is up to the caller to lock loaded.mu before calling this function
60
func load(ctx context.Context, workDir string, model *Model, reqOpts map[string]interface{}, sessionDuration time.Duration) error {
61
62
63
	opts := api.DefaultOptions()
	if err := opts.FromMap(model.Options); err != nil {
		log.Printf("could not load model options: %v", err)
Bruce MacDonald's avatar
Bruce MacDonald committed
64
		return err
65
66
	}

Bruce MacDonald's avatar
Bruce MacDonald committed
67
68
	if err := opts.FromMap(reqOpts); err != nil {
		return err
69
70
	}

71
72
73
74
75
76
77
78
79
80
81
	// check if the loaded model is still running in a subprocess, in case something unexpected happened
	if loaded.llm != nil {
		if err := loaded.llm.Ping(ctx); err != nil {
			log.Print("loaded llm process not responding, closing now")
			// the subprocess is no longer running, so close it
			loaded.llm.Close()
			loaded.llm = nil
			loaded.digest = ""
		}
	}

82
	if model.Digest != loaded.digest || !reflect.DeepEqual(loaded.options, opts) {
Jeffrey Morgan's avatar
Jeffrey Morgan committed
83
		if loaded.llm != nil {
84
			log.Println("changing loaded model")
Jeffrey Morgan's avatar
Jeffrey Morgan committed
85
86
			loaded.llm.Close()
			loaded.llm = nil
87
			loaded.digest = ""
Michael Yang's avatar
Michael Yang committed
88
		}
Michael Yang's avatar
Michael Yang committed
89

90
		llmModel, err := llm.New(workDir, model.ModelPath, model.AdapterPaths, opts)
Michael Yang's avatar
Michael Yang committed
91
		if err != nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
92
			return err
Michael Yang's avatar
Michael Yang committed
93
94
		}

95
96
97
98
99
		// set cache values before modifying opts
		loaded.llm = llmModel
		loaded.digest = model.Digest
		loaded.options = opts

100
		if opts.NumKeep < 0 {
101
			promptWithSystem, err := model.Prompt(api.GenerateRequest{})
102
			if err != nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
103
				return err
104
105
			}

106
			promptNoSystem, err := model.Prompt(api.GenerateRequest{Context: []int{0}})
107
			if err != nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
108
				return err
109
110
			}

111
112
113
114
			tokensWithSystem, err := llmModel.Encode(ctx, promptWithSystem)
			if err != nil {
				return err
			}
Michael Yang's avatar
Michael Yang committed
115

116
117
118
119
			tokensNoSystem, err := llmModel.Encode(ctx, promptNoSystem)
			if err != nil {
				return err
			}
120

Michael Yang's avatar
Michael Yang committed
121
			opts.NumKeep = len(tokensWithSystem) - len(tokensNoSystem)
122

123
124
			llmModel.SetOptions(opts)
		}
Michael Yang's avatar
Michael Yang committed
125
	}
126

Jeffrey Morgan's avatar
Jeffrey Morgan committed
127
	loaded.expireAt = time.Now().Add(sessionDuration)
Bruce MacDonald's avatar
Bruce MacDonald committed
128

Jeffrey Morgan's avatar
Jeffrey Morgan committed
129
130
131
132
	if loaded.expireTimer == nil {
		loaded.expireTimer = time.AfterFunc(sessionDuration, func() {
			loaded.mu.Lock()
			defer loaded.mu.Unlock()
Michael Yang's avatar
Michael Yang committed
133

Jeffrey Morgan's avatar
Jeffrey Morgan committed
134
			if time.Now().Before(loaded.expireAt) {
Michael Yang's avatar
Michael Yang committed
135
136
137
				return
			}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
138
			if loaded.llm == nil {
Michael Yang's avatar
Michael Yang committed
139
140
141
				return
			}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
142
143
			loaded.llm.Close()
			loaded.llm = nil
144
			loaded.digest = ""
Michael Yang's avatar
Michael Yang committed
145
		})
Michael Yang's avatar
Michael Yang committed
146
	}
147

Jeffrey Morgan's avatar
Jeffrey Morgan committed
148
	loaded.expireTimer.Reset(sessionDuration)
Bruce MacDonald's avatar
Bruce MacDonald committed
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
	return nil
}

func GenerateHandler(c *gin.Context) {
	loaded.mu.Lock()
	defer loaded.mu.Unlock()

	checkpointStart := time.Now()

	var req api.GenerateRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	model, err := GetModel(req.Model)
	if err != nil {
166
167
168
169
170
		var pErr *fs.PathError
		if errors.As(err, &pErr) {
			c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("model '%s' not found, try pulling it first", req.Model)})
			return
		}
Bruce MacDonald's avatar
Bruce MacDonald committed
171
172
173
174
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

175
176
177
178
179
	workDir := c.GetString("workDir")

	// TODO: set this duration from the request if specified
	sessionDuration := defaultSessionDuration
	if err := load(c.Request.Context(), workDir, model, req.Options, sessionDuration); err != nil {
180
181
182
183
		if errors.Is(err, api.ErrInvalidOpts) {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}
Bruce MacDonald's avatar
Bruce MacDonald committed
184
185
186
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
Michael Yang's avatar
Michael Yang committed
187

Michael Yang's avatar
Michael Yang committed
188
189
	checkpointLoaded := time.Now()

190
	prompt, err := model.Prompt(req)
Michael Yang's avatar
Michael Yang committed
191
192
193
194
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
195

Michael Yang's avatar
Michael Yang committed
196
197
198
	ch := make(chan any)
	go func() {
		defer close(ch)
Michael Yang's avatar
Michael Yang committed
199
		fn := func(r api.GenerateResponse) {
Jeffrey Morgan's avatar
Jeffrey Morgan committed
200
201
			loaded.expireAt = time.Now().Add(sessionDuration)
			loaded.expireTimer.Reset(sessionDuration)
Michael Yang's avatar
Michael Yang committed
202

Michael Yang's avatar
Michael Yang committed
203
204
205
			r.Model = req.Model
			r.CreatedAt = time.Now().UTC()
			if r.Done {
Michael Yang's avatar
Michael Yang committed
206
207
				r.TotalDuration = time.Since(checkpointStart)
				r.LoadDuration = checkpointLoaded.Sub(checkpointStart)
Michael Yang's avatar
Michael Yang committed
208
209
210
			}

			ch <- r
Michael Yang's avatar
Michael Yang committed
211
212
		}

213
214
		// an empty request loads the model
		if req.Prompt == "" && req.Template == "" && req.System == "" {
Patrick Devine's avatar
Patrick Devine committed
215
216
217
218
219
			ch <- api.GenerateResponse{Model: req.Model, Done: true}
		} else {
			if err := loaded.llm.Predict(c.Request.Context(), req.Context, prompt, fn); err != nil {
				ch <- gin.H{"error": err.Error()}
			}
Michael Yang's avatar
Michael Yang committed
220
		}
Michael Yang's avatar
Michael Yang committed
221
	}()
Michael Yang's avatar
Michael Yang committed
222

223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
	if req.Stream != nil && !*req.Stream {
		var response api.GenerateResponse
		generated := ""
		for resp := range ch {
			if r, ok := resp.(api.GenerateResponse); ok {
				generated += r.Response
				response = r
			} else {
				c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
				return
			}
		}
		response.Response = generated
		c.JSON(http.StatusOK, response)
		return
	}

Michael Yang's avatar
Michael Yang committed
240
	streamResponse(c, ch)
Michael Yang's avatar
Michael Yang committed
241
}
Michael Yang's avatar
Michael Yang committed
242

Bruce MacDonald's avatar
Bruce MacDonald committed
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
func EmbeddingHandler(c *gin.Context) {
	loaded.mu.Lock()
	defer loaded.mu.Unlock()

	var req api.EmbeddingRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	model, err := GetModel(req.Model)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
258
259
260

	workDir := c.GetString("workDir")
	if err := load(c.Request.Context(), workDir, model, req.Options, 5*time.Minute); err != nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
261
262
263
264
265
266
267
268
269
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	if !loaded.options.EmbeddingOnly {
		c.JSON(http.StatusBadRequest, gin.H{"error": "embedding option must be set to true"})
		return
	}

270
	embedding, err := loaded.llm.Embedding(c.Request.Context(), req.Prompt)
Bruce MacDonald's avatar
Bruce MacDonald committed
271
272
273
274
275
276
277
278
279
280
281
282
	if err != nil {
		log.Printf("embedding generation failed: %v", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate embedding"})
		return
	}

	resp := api.EmbeddingResponse{
		Embedding: embedding,
	}
	c.JSON(http.StatusOK, resp)
}

283
func PullModelHandler(c *gin.Context) {
Michael Yang's avatar
Michael Yang committed
284
285
286
287
288
289
	var req api.PullRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

290
291
292
	ch := make(chan any)
	go func() {
		defer close(ch)
293
294
		fn := func(r api.ProgressResponse) {
			ch <- r
295
		}
296

297
298
299
300
		regOpts := &RegistryOptions{
			Insecure: req.Insecure,
		}

301
302
303
304
		ctx, cancel := context.WithCancel(c.Request.Context())
		defer cancel()

		if err := PullModel(ctx, req.Name, regOpts, fn); err != nil {
Michael Yang's avatar
Michael Yang committed
305
			ch <- gin.H{"error": err.Error()}
306
307
308
		}
	}()

309
310
311
312
313
	if req.Stream != nil && !*req.Stream {
		waitForStream(c, ch)
		return
	}

314
315
316
	streamResponse(c, ch)
}

317
func PushModelHandler(c *gin.Context) {
318
319
320
	var req api.PushRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
Michael Yang's avatar
Michael Yang committed
321
322
		return
	}
Michael Yang's avatar
Michael Yang committed
323

324
325
326
	ch := make(chan any)
	go func() {
		defer close(ch)
327
328
		fn := func(r api.ProgressResponse) {
			ch <- r
329
		}
330

331
332
333
334
		regOpts := &RegistryOptions{
			Insecure: req.Insecure,
		}

335
336
		ctx := context.Background()
		if err := PushModel(ctx, req.Name, regOpts, fn); err != nil {
Michael Yang's avatar
Michael Yang committed
337
			ch <- gin.H{"error": err.Error()}
338
339
340
		}
	}()

341
342
343
344
345
	if req.Stream != nil && !*req.Stream {
		waitForStream(c, ch)
		return
	}

346
347
348
	streamResponse(c, ch)
}

349
func CreateModelHandler(c *gin.Context) {
350
351
352
	var req api.CreateRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
Michael Yang's avatar
Michael Yang committed
353
		return
354
355
	}

356
357
	workDir := c.GetString("workDir")

Michael Yang's avatar
Michael Yang committed
358
	ch := make(chan any)
Michael Yang's avatar
Michael Yang committed
359
360
	go func() {
		defer close(ch)
361
362
		fn := func(resp api.ProgressResponse) {
			ch <- resp
363
364
		}

365
366
367
		ctx, cancel := context.WithCancel(c.Request.Context())
		defer cancel()

368
		if err := CreateModel(ctx, workDir, req.Name, req.Path, fn); err != nil {
Michael Yang's avatar
Michael Yang committed
369
			ch <- gin.H{"error": err.Error()}
370
		}
Michael Yang's avatar
Michael Yang committed
371
	}()
Michael Yang's avatar
Michael Yang committed
372

373
374
375
376
377
	if req.Stream != nil && !*req.Stream {
		waitForStream(c, ch)
		return
	}

Michael Yang's avatar
Michael Yang committed
378
	streamResponse(c, ch)
Bruce MacDonald's avatar
Bruce MacDonald committed
379
380
}

381
382
383
384
385
386
387
func DeleteModelHandler(c *gin.Context) {
	var req api.DeleteRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

388
389
390
391
	if err := DeleteModel(req.Name); err != nil {
		if os.IsNotExist(err) {
			c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("model '%s' not found", req.Name)})
		} else {
392
393
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		}
394
395
		return
	}
Michael Yang's avatar
Michael Yang committed
396
397
398
399
400
401
402
403
404
405
406
407

	manifestsPath, err := GetManifestPath()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	if err := PruneDirectory(manifestsPath); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

408
	c.JSON(http.StatusOK, nil)
409
410
}

Patrick Devine's avatar
Patrick Devine committed
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
func ShowModelHandler(c *gin.Context) {
	var req api.ShowRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	resp, err := GetModelInfo(req.Name)
	if err != nil {
		if os.IsNotExist(err) {
			c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("model '%s' not found", req.Name)})
		} else {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		}
		return
	}

	c.JSON(http.StatusOK, resp)
}

func GetModelInfo(name string) (*api.ShowResponse, error) {
	model, err := GetModel(name)
	if err != nil {
		return nil, err
	}

	resp := &api.ShowResponse{
		License:  strings.Join(model.License, "\n"),
		System:   model.System,
		Template: model.Template,
	}

	mf, err := ShowModelfile(model)
	if err != nil {
		return nil, err
	}

	resp.Modelfile = mf

	var params []string
	cs := 30
	for k, v := range model.Options {
		switch val := v.(type) {
		case string:
			params = append(params, fmt.Sprintf("%-*s %s", cs, k, val))
		case int:
			params = append(params, fmt.Sprintf("%-*s %s", cs, k, strconv.Itoa(val)))
		case float64:
			params = append(params, fmt.Sprintf("%-*s %s", cs, k, strconv.FormatFloat(val, 'f', 0, 64)))
		case bool:
			params = append(params, fmt.Sprintf("%-*s %s", cs, k, strconv.FormatBool(val)))
		case []interface{}:
			for _, nv := range val {
				switch nval := nv.(type) {
				case string:
					params = append(params, fmt.Sprintf("%-*s %s", cs, k, nval))
				case int:
					params = append(params, fmt.Sprintf("%-*s %s", cs, k, strconv.Itoa(nval)))
				case float64:
					params = append(params, fmt.Sprintf("%-*s %s", cs, k, strconv.FormatFloat(nval, 'f', 0, 64)))
				case bool:
					params = append(params, fmt.Sprintf("%-*s %s", cs, k, strconv.FormatBool(nval)))
				}
			}
		}
	}
	resp.Parameters = strings.Join(params, "\n")

	return resp, nil
}

482
func ListModelsHandler(c *gin.Context) {
483
	var models []api.ModelResponse
Patrick Devine's avatar
Patrick Devine committed
484
485
486
487
488
	fp, err := GetManifestPath()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
Michael Yang's avatar
Michael Yang committed
489
490

	walkFunc := func(path string, info os.FileInfo, _ error) error {
Patrick Devine's avatar
Patrick Devine committed
491
		if !info.IsDir() {
Michael Yang's avatar
Michael Yang committed
492
493
494
			dir, file := filepath.Split(path)
			dir = strings.Trim(strings.TrimPrefix(dir, fp), string(os.PathSeparator))
			tag := strings.Join([]string{dir, file}, ":")
495

496
			mp := ParseModelPath(tag)
Patrick Devine's avatar
Patrick Devine committed
497
			manifest, digest, err := GetManifest(mp)
Patrick Devine's avatar
Patrick Devine committed
498
			if err != nil {
499
500
				log.Printf("skipping file: %s", fp)
				return nil
Patrick Devine's avatar
Patrick Devine committed
501
			}
Michael Yang's avatar
Michael Yang committed
502
503

			models = append(models, api.ModelResponse{
Patrick Devine's avatar
Patrick Devine committed
504
505
				Name:       mp.GetShortTagname(),
				Size:       manifest.GetTotalSize(),
Patrick Devine's avatar
Patrick Devine committed
506
				Digest:     digest,
Michael Yang's avatar
Michael Yang committed
507
508
				ModifiedAt: info.ModTime(),
			})
Patrick Devine's avatar
Patrick Devine committed
509
		}
Michael Yang's avatar
Michael Yang committed
510

Patrick Devine's avatar
Patrick Devine committed
511
		return nil
Michael Yang's avatar
Michael Yang committed
512
513
514
	}

	if err := filepath.Walk(fp, walkFunc); err != nil {
Patrick Devine's avatar
Patrick Devine committed
515
516
517
518
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

Michael Yang's avatar
Michael Yang committed
519
	c.JSON(http.StatusOK, api.ListResponse{Models: models})
Patrick Devine's avatar
Patrick Devine committed
520
521
}

Patrick Devine's avatar
Patrick Devine committed
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
func CopyModelHandler(c *gin.Context) {
	var req api.CopyRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	if err := CopyModel(req.Source, req.Destination); err != nil {
		if os.IsNotExist(err) {
			c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("model '%s' not found", req.Source)})
		} else {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		}
		return
	}
}

Michael Yang's avatar
Michael Yang committed
539
540
541
542
543
544
545
var defaultAllowOrigins = []string{
	"localhost",
	"127.0.0.1",
	"0.0.0.0",
}

func Serve(ln net.Listener, allowOrigins []string) error {
Michael Yang's avatar
Michael Yang committed
546
547
	config := cors.DefaultConfig()
	config.AllowWildcard = true
Michael Yang's avatar
Michael Yang committed
548
549
550
551
552
553
554
555
556
557

	config.AllowOrigins = allowOrigins
	for _, allowOrigin := range defaultAllowOrigins {
		config.AllowOrigins = append(config.AllowOrigins,
			fmt.Sprintf("http://%s", allowOrigin),
			fmt.Sprintf("https://%s", allowOrigin),
			fmt.Sprintf("http://%s:*", allowOrigin),
			fmt.Sprintf("https://%s:*", allowOrigin),
		)
	}
Michael Yang's avatar
Michael Yang committed
558

559
560
561
562
563
564
	workDir, err := os.MkdirTemp("", "ollama")
	if err != nil {
		return err
	}
	defer os.RemoveAll(workDir)

Bruce MacDonald's avatar
Bruce MacDonald committed
565
	r := gin.Default()
566
567
568
569
570
571
572
	r.Use(
		cors.New(config),
		func(c *gin.Context) {
			c.Set("workDir", workDir)
			c.Next()
		},
	)
Bruce MacDonald's avatar
Bruce MacDonald committed
573

574
575
	r.POST("/api/pull", PullModelHandler)
	r.POST("/api/generate", GenerateHandler)
Bruce MacDonald's avatar
Bruce MacDonald committed
576
	r.POST("/api/embeddings", EmbeddingHandler)
577
578
	r.POST("/api/create", CreateModelHandler)
	r.POST("/api/push", PushModelHandler)
Patrick Devine's avatar
Patrick Devine committed
579
	r.POST("/api/copy", CopyModelHandler)
580
	r.DELETE("/api/delete", DeleteModelHandler)
Patrick Devine's avatar
Patrick Devine committed
581
	r.POST("/api/show", ShowModelHandler)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
582

Michael Yang's avatar
Michael Yang committed
583
584
585
586
587
588
589
590
	for _, method := range []string{http.MethodGet, http.MethodHead} {
		r.Handle(method, "/", func(c *gin.Context) {
			c.String(http.StatusOK, "Ollama is running")
		})

		r.Handle(method, "/api/tags", ListModelsHandler)
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
591
592
593
594
595
	log.Printf("Listening on %s", ln.Addr())
	s := &http.Server{
		Handler: r,
	}

596
597
	// listen for a ctrl+c and stop any loaded llm
	signals := make(chan os.Signal, 1)
598
	signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
599
600
	go func() {
		<-signals
601
602
603
		if loaded.llm != nil {
			loaded.llm.Close()
		}
604
		os.RemoveAll(workDir)
605
606
607
		os.Exit(0)
	}()

608
609
610
	if runtime.GOOS == "linux" {
		// check compatibility to log warnings
		if _, err := llm.CheckVRAM(); err != nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
611
			log.Printf("Warning: GPU support may not enabled, check you have installed install GPU drivers: %v", err)
612
613
614
		}
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
615
616
	return s.Serve(ln)
}
Michael Yang's avatar
Michael Yang committed
617

618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
func waitForStream(c *gin.Context, ch chan interface{}) {
	c.Header("Content-Type", "application/json")
	for resp := range ch {
		switch r := resp.(type) {
		case api.ProgressResponse:
			if r.Status == "success" {
				c.JSON(http.StatusOK, r)
				return
			}
		case gin.H:
			if errorMsg, ok := r["error"].(string); ok {
				c.JSON(http.StatusInternalServerError, gin.H{"error": errorMsg})
				return
			} else {
				c.JSON(http.StatusInternalServerError, gin.H{"error": "unexpected error format in progress response"})
				return
			}
		default:
			c.JSON(http.StatusInternalServerError, gin.H{"error": "unexpected progress response"})
			return
		}
	}
	c.JSON(http.StatusInternalServerError, gin.H{"error": "unexpected end of progress response"})
}

Michael Yang's avatar
Michael Yang committed
643
func streamResponse(c *gin.Context, ch chan any) {
644
	c.Header("Content-Type", "application/x-ndjson")
Michael Yang's avatar
Michael Yang committed
645
646
647
648
649
650
651
652
	c.Stream(func(w io.Writer) bool {
		val, ok := <-ch
		if !ok {
			return false
		}

		bts, err := json.Marshal(val)
		if err != nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
653
			log.Printf("streamResponse: json.Marshal failed with %s", err)
Michael Yang's avatar
Michael Yang committed
654
655
656
			return false
		}

657
		// Delineate chunks with new-line delimiter
Michael Yang's avatar
Michael Yang committed
658
659
		bts = append(bts, '\n')
		if _, err := w.Write(bts); err != nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
660
			log.Printf("streamResponse: w.Write failed with %s", err)
Michael Yang's avatar
Michael Yang committed
661
662
663
664
665
666
			return false
		}

		return true
	})
}