routes.go 7.7 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"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
23
24
)

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

	llm *llama.LLM

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

33
34
	digest  string
	options api.Options
Michael Yang's avatar
Michael Yang committed
35
36
}

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

Michael Yang's avatar
Michael Yang committed
41
	checkpointStart := time.Now()
42

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

49
50
51
52
	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
53
	}
Michael Yang's avatar
Michael Yang committed
54

Jeffrey Morgan's avatar
Jeffrey Morgan committed
55
56
57
58
	if model.Digest != loaded.digest || !reflect.DeepEqual(loaded.options, req.Options) {
		if loaded.llm != nil {
			loaded.llm.Close()
			loaded.llm = nil
59
			loaded.digest = ""
Michael Yang's avatar
Michael Yang committed
60
		}
Michael Yang's avatar
Michael Yang committed
61

Michael Yang's avatar
Michael Yang committed
62
		opts := api.DefaultOptions()
63
64
		if err := opts.FromMap(model.Options); err != nil {
			log.Printf("could not load model options: %v", err)
Michael Yang's avatar
Michael Yang committed
65
66
67
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}
68
69
		if err := opts.FromMap(req.Options); err != nil {
			log.Printf("could not merge model options: %v", err)
Michael Yang's avatar
Michael Yang committed
70
71
72
73
74
75
76
77
78
79
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}

		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
80
81
		loaded.llm = llm
		loaded.digest = model.Digest
Michael Yang's avatar
Michael Yang committed
82
83
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
84
	sessionDuration := 5 * time.Minute
Michael Yang's avatar
Michael Yang committed
85

Jeffrey Morgan's avatar
Jeffrey Morgan committed
86
87
88
89
90
	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
91

Jeffrey Morgan's avatar
Jeffrey Morgan committed
92
			if time.Now().Before(loaded.expireAt) {
Michael Yang's avatar
Michael Yang committed
93
94
95
				return
			}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
96
			if loaded.llm == nil {
Michael Yang's avatar
Michael Yang committed
97
98
99
				return
			}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
100
101
			loaded.llm.Close()
			loaded.llm = nil
102
			loaded.digest = ""
Michael Yang's avatar
Michael Yang committed
103
		})
Michael Yang's avatar
Michael Yang committed
104
	}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
105
	loaded.expireTimer.Reset(sessionDuration)
Michael Yang's avatar
Michael Yang committed
106

Michael Yang's avatar
Michael Yang committed
107
108
	checkpointLoaded := time.Now()

Michael Yang's avatar
Michael Yang committed
109
	prompt, err := model.Prompt(req)
Michael Yang's avatar
Michael Yang committed
110
111
112
113
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
114

Michael Yang's avatar
Michael Yang committed
115
116
117
	ch := make(chan any)
	go func() {
		defer close(ch)
Michael Yang's avatar
Michael Yang committed
118
		fn := func(r api.GenerateResponse) {
Jeffrey Morgan's avatar
Jeffrey Morgan committed
119
120
			loaded.expireAt = time.Now().Add(sessionDuration)
			loaded.expireTimer.Reset(sessionDuration)
Michael Yang's avatar
Michael Yang committed
121

Michael Yang's avatar
Michael Yang committed
122
123
124
			r.Model = req.Model
			r.CreatedAt = time.Now().UTC()
			if r.Done {
Michael Yang's avatar
Michael Yang committed
125
126
				r.TotalDuration = time.Since(checkpointStart)
				r.LoadDuration = checkpointLoaded.Sub(checkpointStart)
Michael Yang's avatar
Michael Yang committed
127
128
129
			}

			ch <- r
Michael Yang's avatar
Michael Yang committed
130
131
		}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
132
		if err := loaded.llm.Predict(req.Context, prompt, fn); err != nil {
Michael Yang's avatar
Michael Yang committed
133
134
			ch <- gin.H{"error": err.Error()}
		}
Michael Yang's avatar
Michael Yang committed
135
	}()
Michael Yang's avatar
Michael Yang committed
136

Michael Yang's avatar
Michael Yang committed
137
	streamResponse(c, ch)
Michael Yang's avatar
Michael Yang committed
138
}
Michael Yang's avatar
Michael Yang committed
139

140
func PullModelHandler(c *gin.Context) {
Michael Yang's avatar
Michael Yang committed
141
142
143
144
145
146
	var req api.PullRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

147
148
149
	ch := make(chan any)
	go func() {
		defer close(ch)
150
151
		fn := func(r api.ProgressResponse) {
			ch <- r
152
		}
153

154
155
156
157
158
159
160
		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
161
			ch <- gin.H{"error": err.Error()}
162
163
164
165
166
167
		}
	}()

	streamResponse(c, ch)
}

168
func PushModelHandler(c *gin.Context) {
169
170
171
	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
172
173
		return
	}
Michael Yang's avatar
Michael Yang committed
174

175
176
177
	ch := make(chan any)
	go func() {
		defer close(ch)
178
179
		fn := func(r api.ProgressResponse) {
			ch <- r
180
		}
181

182
183
184
185
186
187
188
		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
189
			ch <- gin.H{"error": err.Error()}
190
191
192
193
194
195
		}
	}()

	streamResponse(c, ch)
}

196
func CreateModelHandler(c *gin.Context) {
197
198
199
	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
200
		return
201
202
	}

Michael Yang's avatar
Michael Yang committed
203
	ch := make(chan any)
Michael Yang's avatar
Michael Yang committed
204
205
	go func() {
		defer close(ch)
206
207
		fn := func(resp api.ProgressResponse) {
			ch <- resp
208
209
		}

210
		if err := CreateModel(req.Name, req.Path, fn); err != nil {
Michael Yang's avatar
Michael Yang committed
211
			ch <- gin.H{"error": err.Error()}
212
		}
Michael Yang's avatar
Michael Yang committed
213
	}()
Michael Yang's avatar
Michael Yang committed
214

Michael Yang's avatar
Michael Yang committed
215
	streamResponse(c, ch)
Bruce MacDonald's avatar
Bruce MacDonald committed
216
217
}

218
219
220
221
222
223
224
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
	}

225
226
227
228
	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 {
229
230
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		}
231
232
		return
	}
233
234
235
}

func ListModelsHandler(c *gin.Context) {
Patrick Devine's avatar
Patrick Devine committed
236
237
238
239
240
241
242
243
	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 {
244
245
246
247
			if errors.Is(err, os.ErrNotExist) {
				log.Printf("manifest file does not exist: %s", fp)
				return nil
			}
Patrick Devine's avatar
Patrick Devine committed
248
249
250
251
252
			return err
		}
		if !info.IsDir() {
			fi, err := os.Stat(path)
			if err != nil {
253
254
				log.Printf("skipping file: %s", fp)
				return nil
Patrick Devine's avatar
Patrick Devine committed
255
256
257
258
259
260
261
262
263
264
			}
			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 {
265
266
				log.Printf("skipping file: %s", fp)
				return nil
Patrick Devine's avatar
Patrick Devine committed
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
			}
			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
282
	c.JSON(http.StatusOK, api.ListResponse{Models: models})
Patrick Devine's avatar
Patrick Devine committed
283
284
}

Patrick Devine's avatar
Patrick Devine committed
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
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
	}
}

Bruce MacDonald's avatar
Bruce MacDonald committed
302
func Serve(ln net.Listener) error {
Michael Yang's avatar
Michael Yang committed
303
304
305
306
307
308
309
310
311
312
313
314
315
316
	config := cors.DefaultConfig()
	config.AllowWildcard = true
	// only allow http/https from localhost
	config.AllowOrigins = []string{
		"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:*",
	}

Bruce MacDonald's avatar
Bruce MacDonald committed
317
	r := gin.Default()
Michael Yang's avatar
Michael Yang committed
318
	r.Use(cors.New(config))
Bruce MacDonald's avatar
Bruce MacDonald committed
319

320
321
322
	r.GET("/", func(c *gin.Context) {
		c.String(http.StatusOK, "Ollama is running")
	})
Bruce MacDonald's avatar
Bruce MacDonald committed
323
324
325
	r.HEAD("/", func(c *gin.Context) {
		c.Status(http.StatusOK)
	})
326

327
328
329
330
	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
331
	r.POST("/api/copy", CopyModelHandler)
332
333
	r.GET("/api/tags", ListModelsHandler)
	r.DELETE("/api/delete", DeleteModelHandler)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
334
335
336
337
338
339
340
341

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

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

Michael Yang's avatar
Michael Yang committed
343
func streamResponse(c *gin.Context, ch chan any) {
Michael Yang's avatar
Michael Yang committed
344
345
346
347
348
349
350
351
	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
352
			log.Printf("streamResponse: json.Marshal failed with %s", err)
Michael Yang's avatar
Michael Yang committed
353
354
355
356
357
			return false
		}

		bts = append(bts, '\n')
		if _, err := w.Write(bts); err != nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
358
			log.Printf("streamResponse: w.Write failed with %s", err)
Michael Yang's avatar
Michael Yang committed
359
360
361
362
363
364
			return false
		}

		return true
	})
}