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

import (
Michael Yang's avatar
Michael Yang committed
4
	"encoding/json"
5
	"errors"
6
	"fmt"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
7
8
9
10
	"io"
	"log"
	"net"
	"net/http"
11
	"os"
Michael Yang's avatar
Michael Yang committed
12
	"path/filepath"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
13
	"reflect"
Michael Yang's avatar
Michael Yang committed
14
	"strings"
Michael Yang's avatar
Michael Yang committed
15
	"sync"
16
	"time"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
17

Michael Yang's avatar
Michael Yang committed
18
	"github.com/gin-contrib/cors"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
19
20
	"github.com/gin-gonic/gin"

Jeffrey Morgan's avatar
Jeffrey Morgan committed
21
	"github.com/jmorganca/ollama/api"
Michael Yang's avatar
Michael Yang committed
22
	"github.com/jmorganca/ollama/llama"
23
	"github.com/jmorganca/ollama/vector"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
24
25
)

Jeffrey Morgan's avatar
Jeffrey Morgan committed
26
var loaded struct {
Michael Yang's avatar
Michael Yang committed
27
28
	mu sync.Mutex

29
30
	llm        *llama.LLM
	Embeddings []vector.Embedding
Michael Yang's avatar
Michael Yang committed
31
32
33

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

35
36
	digest  string
	options api.Options
Michael Yang's avatar
Michael Yang committed
37
38
}

39
func GenerateHandler(c *gin.Context) {
Jeffrey Morgan's avatar
Jeffrey Morgan committed
40
41
	loaded.mu.Lock()
	defer loaded.mu.Unlock()
Michael Yang's avatar
Michael Yang committed
42

Michael Yang's avatar
Michael Yang committed
43
	checkpointStart := time.Now()
44

Michael Yang's avatar
Michael Yang committed
45
	var req api.GenerateRequest
Bruce MacDonald's avatar
Bruce MacDonald committed
46
	if err := c.ShouldBindJSON(&req); err != nil {
47
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
Bruce MacDonald's avatar
Bruce MacDonald committed
48
49
		return
	}
50

51
52
53
54
	model, err := GetModel(req.Model)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
Bruce MacDonald's avatar
Bruce MacDonald committed
55
	}
Michael Yang's avatar
Michael Yang committed
56

57
58
59
60
61
62
63
64
65
66
67
68
69
70
	opts := api.DefaultOptions()
	if err := opts.FromMap(model.Options); err != nil {
		log.Printf("could not load model options: %v", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	if err := opts.FromMap(req.Options); err != nil {
		log.Printf("could not merge model options: %v", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	if model.Digest != loaded.digest || !reflect.DeepEqual(loaded.options, opts) {
Jeffrey Morgan's avatar
Jeffrey Morgan committed
71
72
73
		if loaded.llm != nil {
			loaded.llm.Close()
			loaded.llm = nil
74
			loaded.digest = ""
Michael Yang's avatar
Michael Yang committed
75
		}
Michael Yang's avatar
Michael Yang committed
76

77
78
79
80
81
		if model.Embeddings != nil && len(model.Embeddings) > 0 {
			opts.EmbeddingOnly = true // this is requried to generate embeddings, completions will still work
			loaded.Embeddings = model.Embeddings
		}

Michael Yang's avatar
Michael Yang committed
82
83
84
85
86
87
		llm, err := llama.New(model.ModelPath, opts)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
88
89
		loaded.llm = llm
		loaded.digest = model.Digest
90
		loaded.options = opts
Michael Yang's avatar
Michael Yang committed
91
	}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
92
	sessionDuration := 5 * time.Minute
Michael Yang's avatar
Michael Yang committed
93

Jeffrey Morgan's avatar
Jeffrey Morgan committed
94
95
96
97
98
	loaded.expireAt = time.Now().Add(sessionDuration)
	if loaded.expireTimer == nil {
		loaded.expireTimer = time.AfterFunc(sessionDuration, func() {
			loaded.mu.Lock()
			defer loaded.mu.Unlock()
Michael Yang's avatar
Michael Yang committed
99

Jeffrey Morgan's avatar
Jeffrey Morgan committed
100
			if time.Now().Before(loaded.expireAt) {
Michael Yang's avatar
Michael Yang committed
101
102
103
				return
			}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
104
			if loaded.llm == nil {
Michael Yang's avatar
Michael Yang committed
105
106
107
				return
			}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
108
109
			loaded.llm.Close()
			loaded.llm = nil
110
			loaded.digest = ""
Michael Yang's avatar
Michael Yang committed
111
		})
Michael Yang's avatar
Michael Yang committed
112
	}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
113
	loaded.expireTimer.Reset(sessionDuration)
Michael Yang's avatar
Michael Yang committed
114

Michael Yang's avatar
Michael Yang committed
115
116
	checkpointLoaded := time.Now()

Michael Yang's avatar
Michael Yang committed
117
	prompt, err := model.Prompt(req)
Michael Yang's avatar
Michael Yang committed
118
119
120
121
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
122

Michael Yang's avatar
Michael Yang committed
123
124
125
	ch := make(chan any)
	go func() {
		defer close(ch)
Michael Yang's avatar
Michael Yang committed
126
		fn := func(r api.GenerateResponse) {
Jeffrey Morgan's avatar
Jeffrey Morgan committed
127
128
			loaded.expireAt = time.Now().Add(sessionDuration)
			loaded.expireTimer.Reset(sessionDuration)
Michael Yang's avatar
Michael Yang committed
129

Michael Yang's avatar
Michael Yang committed
130
131
132
			r.Model = req.Model
			r.CreatedAt = time.Now().UTC()
			if r.Done {
Michael Yang's avatar
Michael Yang committed
133
134
				r.TotalDuration = time.Since(checkpointStart)
				r.LoadDuration = checkpointLoaded.Sub(checkpointStart)
Michael Yang's avatar
Michael Yang committed
135
136
137
			}

			ch <- r
Michael Yang's avatar
Michael Yang committed
138
139
		}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
140
		if err := loaded.llm.Predict(req.Context, prompt, fn); err != nil {
Michael Yang's avatar
Michael Yang committed
141
142
			ch <- gin.H{"error": err.Error()}
		}
Michael Yang's avatar
Michael Yang committed
143
	}()
Michael Yang's avatar
Michael Yang committed
144

Michael Yang's avatar
Michael Yang committed
145
	streamResponse(c, ch)
Michael Yang's avatar
Michael Yang committed
146
}
Michael Yang's avatar
Michael Yang committed
147

148
func PullModelHandler(c *gin.Context) {
Michael Yang's avatar
Michael Yang committed
149
150
151
152
153
154
	var req api.PullRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

155
156
157
	ch := make(chan any)
	go func() {
		defer close(ch)
158
159
		fn := func(r api.ProgressResponse) {
			ch <- r
160
		}
161

162
163
164
165
166
167
168
		regOpts := &RegistryOptions{
			Insecure: req.Insecure,
			Username: req.Username,
			Password: req.Password,
		}

		if err := PullModel(req.Name, regOpts, fn); err != nil {
Michael Yang's avatar
Michael Yang committed
169
			ch <- gin.H{"error": err.Error()}
170
171
172
173
174
175
		}
	}()

	streamResponse(c, ch)
}

176
func PushModelHandler(c *gin.Context) {
177
178
179
	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
180
181
		return
	}
Michael Yang's avatar
Michael Yang committed
182

183
184
185
	ch := make(chan any)
	go func() {
		defer close(ch)
186
187
		fn := func(r api.ProgressResponse) {
			ch <- r
188
		}
189

190
191
192
193
194
195
196
		regOpts := &RegistryOptions{
			Insecure: req.Insecure,
			Username: req.Username,
			Password: req.Password,
		}

		if err := PushModel(req.Name, regOpts, fn); err != nil {
Michael Yang's avatar
Michael Yang committed
197
			ch <- gin.H{"error": err.Error()}
198
199
200
201
202
203
		}
	}()

	streamResponse(c, ch)
}

204
func CreateModelHandler(c *gin.Context) {
205
206
207
	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
208
		return
209
210
	}

Michael Yang's avatar
Michael Yang committed
211
	ch := make(chan any)
Michael Yang's avatar
Michael Yang committed
212
213
	go func() {
		defer close(ch)
214
215
		fn := func(resp api.ProgressResponse) {
			ch <- resp
216
217
		}

218
		if err := CreateModel(req.Name, req.Path, fn); err != nil {
Michael Yang's avatar
Michael Yang committed
219
			ch <- gin.H{"error": err.Error()}
220
		}
Michael Yang's avatar
Michael Yang committed
221
	}()
Michael Yang's avatar
Michael Yang committed
222

Michael Yang's avatar
Michael Yang committed
223
	streamResponse(c, ch)
Bruce MacDonald's avatar
Bruce MacDonald committed
224
225
}

226
227
228
229
230
231
232
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
	}

233
234
235
236
	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 {
237
238
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		}
239
240
		return
	}
241
242
243
}

func ListModelsHandler(c *gin.Context) {
Patrick Devine's avatar
Patrick Devine committed
244
245
246
247
248
249
250
251
	var models []api.ListResponseModel
	fp, err := GetManifestPath()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	err = filepath.Walk(fp, func(path string, info os.FileInfo, err error) error {
		if err != nil {
252
253
254
255
			if errors.Is(err, os.ErrNotExist) {
				log.Printf("manifest file does not exist: %s", fp)
				return nil
			}
Patrick Devine's avatar
Patrick Devine committed
256
257
258
259
260
			return err
		}
		if !info.IsDir() {
			fi, err := os.Stat(path)
			if err != nil {
261
262
				log.Printf("skipping file: %s", fp)
				return nil
Patrick Devine's avatar
Patrick Devine committed
263
264
265
266
267
268
269
270
271
272
			}
			path := path[len(fp)+1:]
			slashIndex := strings.LastIndex(path, "/")
			if slashIndex == -1 {
				return nil
			}
			tag := path[:slashIndex] + ":" + path[slashIndex+1:]
			mp := ParseModelPath(tag)
			manifest, err := GetManifest(mp)
			if err != nil {
273
274
				log.Printf("skipping file: %s", fp)
				return nil
Patrick Devine's avatar
Patrick Devine committed
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
			}
			model := api.ListResponseModel{
				Name:       mp.GetShortTagname(),
				Size:       manifest.GetTotalSize(),
				ModifiedAt: fi.ModTime(),
			}
			models = append(models, model)
		}
		return nil
	})
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

Michael Yang's avatar
Michael Yang committed
290
	c.JSON(http.StatusOK, api.ListResponse{Models: models})
Patrick Devine's avatar
Patrick Devine committed
291
292
}

Patrick Devine's avatar
Patrick Devine committed
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
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
	}
}

310
func Serve(ln net.Listener, extraOrigins []string) error {
Michael Yang's avatar
Michael Yang committed
311
312
313
	config := cors.DefaultConfig()
	config.AllowWildcard = true
	// only allow http/https from localhost
314
	allowedOrigins := []string{
Michael Yang's avatar
Michael Yang committed
315
316
317
318
319
320
321
322
323
		"http://localhost",
		"http://localhost:*",
		"https://localhost",
		"https://localhost:*",
		"http://127.0.0.1",
		"http://127.0.0.1:*",
		"https://127.0.0.1",
		"https://127.0.0.1:*",
	}
324
325
	allowedOrigins = append(allowedOrigins, extraOrigins...)
	config.AllowOrigins = allowedOrigins
Michael Yang's avatar
Michael Yang committed
326

Bruce MacDonald's avatar
Bruce MacDonald committed
327
	r := gin.Default()
Michael Yang's avatar
Michael Yang committed
328
	r.Use(cors.New(config))
Bruce MacDonald's avatar
Bruce MacDonald committed
329

330
331
332
	r.GET("/", func(c *gin.Context) {
		c.String(http.StatusOK, "Ollama is running")
	})
Bruce MacDonald's avatar
Bruce MacDonald committed
333
334
335
	r.HEAD("/", func(c *gin.Context) {
		c.Status(http.StatusOK)
	})
336

337
338
339
340
	r.POST("/api/pull", PullModelHandler)
	r.POST("/api/generate", GenerateHandler)
	r.POST("/api/create", CreateModelHandler)
	r.POST("/api/push", PushModelHandler)
Patrick Devine's avatar
Patrick Devine committed
341
	r.POST("/api/copy", CopyModelHandler)
342
343
	r.GET("/api/tags", ListModelsHandler)
	r.DELETE("/api/delete", DeleteModelHandler)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
344
345
346
347
348
349
350
351

	log.Printf("Listening on %s", ln.Addr())
	s := &http.Server{
		Handler: r,
	}

	return s.Serve(ln)
}
Michael Yang's avatar
Michael Yang committed
352

Michael Yang's avatar
Michael Yang committed
353
func streamResponse(c *gin.Context, ch chan any) {
Michael Yang's avatar
Michael Yang committed
354
355
356
357
358
359
360
361
	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
362
			log.Printf("streamResponse: json.Marshal failed with %s", err)
Michael Yang's avatar
Michael Yang committed
363
364
365
366
367
			return false
		}

		bts = append(bts, '\n')
		if _, err := w.Write(bts); err != nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
368
			log.Printf("streamResponse: w.Write failed with %s", err)
Michael Yang's avatar
Michael Yang committed
369
370
371
372
373
374
			return false
		}

		return true
	})
}