routes.go 7.6 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
Michael Yang's avatar
Michael Yang committed
59
		}
Michael Yang's avatar
Michael Yang committed
60

61
62
63
64
65
66
67
68
		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)
Michael Yang's avatar
Michael Yang committed
69
70
71
72
73
74
75
76
77
78
			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
79
80
		loaded.llm = llm
		loaded.digest = model.Digest
Michael Yang's avatar
Michael Yang committed
81
82
	}

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

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

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

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

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

Michael Yang's avatar
Michael Yang committed
105
106
	checkpointLoaded := time.Now()

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

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

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

			ch <- r
Michael Yang's avatar
Michael Yang committed
128
129
		}

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

Michael Yang's avatar
Michael Yang committed
135
	streamResponse(c, ch)
Michael Yang's avatar
Michael Yang committed
136
}
Michael Yang's avatar
Michael Yang committed
137

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

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

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

	streamResponse(c, ch)
}

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

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

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

	streamResponse(c, ch)
}

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

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

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

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

216
217
218
219
220
221
222
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
	}

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

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

Patrick Devine's avatar
Patrick Devine committed
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
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
300
func Serve(ln net.Listener) error {
Michael Yang's avatar
Michael Yang committed
301
302
303
304
305
306
307
308
309
310
311
312
313
314
	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
315
	r := gin.Default()
Michael Yang's avatar
Michael Yang committed
316
	r.Use(cors.New(config))
Bruce MacDonald's avatar
Bruce MacDonald committed
317

318
319
320
321
	r.GET("/", func(c *gin.Context) {
		c.String(http.StatusOK, "Ollama is running")
	})

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

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

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

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

		bts = append(bts, '\n')
		if _, err := w.Write(bts); err != nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
353
			log.Printf("streamResponse: w.Write failed with %s", err)
Michael Yang's avatar
Michael Yang committed
354
355
356
357
358
359
			return false
		}

		return true
	})
}