routes.go 7.39 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
	"dario.cat/mergo"
Michael Yang's avatar
Michael Yang committed
19
	"github.com/gin-contrib/cors"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
20
21
	"github.com/gin-gonic/gin"

Jeffrey Morgan's avatar
Jeffrey Morgan committed
22
	"github.com/jmorganca/ollama/api"
Michael Yang's avatar
Michael Yang committed
23
	"github.com/jmorganca/ollama/llama"
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
29
30
31
32
	mu sync.Mutex

	llm *llama.LLM

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

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

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

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

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

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

Jeffrey Morgan's avatar
Jeffrey Morgan committed
56
57
58
59
	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
60
		}
Michael Yang's avatar
Michael Yang committed
61

62
		opts := model.Options
Michael Yang's avatar
Michael Yang committed
63
64
65
66
67
68
69
70
71
72
73
		if err := mergo.Merge(&opts, req.Options, mergo.WithOverride); err != nil {
			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
74
75
		loaded.llm = llm
		loaded.digest = model.Digest
Michael Yang's avatar
Michael Yang committed
76
77
	}

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

Jeffrey Morgan's avatar
Jeffrey Morgan committed
80
81
82
83
84
	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
85

Jeffrey Morgan's avatar
Jeffrey Morgan committed
86
			if time.Now().Before(loaded.expireAt) {
Michael Yang's avatar
Michael Yang committed
87
88
89
				return
			}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
90
			if loaded.llm == nil {
Michael Yang's avatar
Michael Yang committed
91
92
93
				return
			}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
94
95
			loaded.llm.Close()
			loaded.llm = nil
Michael Yang's avatar
Michael Yang committed
96
		})
Michael Yang's avatar
Michael Yang committed
97
	}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
98
	loaded.expireTimer.Reset(sessionDuration)
Michael Yang's avatar
Michael Yang committed
99

Michael Yang's avatar
Michael Yang committed
100
101
	checkpointLoaded := time.Now()

Michael Yang's avatar
Michael Yang committed
102
	prompt, err := model.Prompt(req)
Michael Yang's avatar
Michael Yang committed
103
104
105
106
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
107

Michael Yang's avatar
Michael Yang committed
108
109
110
	ch := make(chan any)
	go func() {
		defer close(ch)
Michael Yang's avatar
Michael Yang committed
111
		fn := func(r api.GenerateResponse) {
Jeffrey Morgan's avatar
Jeffrey Morgan committed
112
113
			loaded.expireAt = time.Now().Add(sessionDuration)
			loaded.expireTimer.Reset(sessionDuration)
Michael Yang's avatar
Michael Yang committed
114

Michael Yang's avatar
Michael Yang committed
115
116
117
			r.Model = req.Model
			r.CreatedAt = time.Now().UTC()
			if r.Done {
Michael Yang's avatar
Michael Yang committed
118
119
				r.TotalDuration = time.Since(checkpointStart)
				r.LoadDuration = checkpointLoaded.Sub(checkpointStart)
Michael Yang's avatar
Michael Yang committed
120
121
122
			}

			ch <- r
Michael Yang's avatar
Michael Yang committed
123
124
		}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
125
		if err := loaded.llm.Predict(req.Context, prompt, fn); err != nil {
Michael Yang's avatar
Michael Yang committed
126
127
			ch <- gin.H{"error": err.Error()}
		}
Michael Yang's avatar
Michael Yang committed
128
	}()
Michael Yang's avatar
Michael Yang committed
129

Michael Yang's avatar
Michael Yang committed
130
	streamResponse(c, ch)
Michael Yang's avatar
Michael Yang committed
131
}
Michael Yang's avatar
Michael Yang committed
132

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

140
141
142
	ch := make(chan any)
	go func() {
		defer close(ch)
143
144
		fn := func(r api.ProgressResponse) {
			ch <- r
145
		}
146

147
148
149
150
151
152
153
		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
154
			ch <- gin.H{"error": err.Error()}
155
156
157
158
159
160
		}
	}()

	streamResponse(c, ch)
}

161
func PushModelHandler(c *gin.Context) {
162
163
164
	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
165
166
		return
	}
Michael Yang's avatar
Michael Yang committed
167

168
169
170
	ch := make(chan any)
	go func() {
		defer close(ch)
171
172
		fn := func(r api.ProgressResponse) {
			ch <- r
173
		}
174

175
176
177
178
179
180
181
		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
182
			ch <- gin.H{"error": err.Error()}
183
184
185
186
187
188
		}
	}()

	streamResponse(c, ch)
}

189
func CreateModelHandler(c *gin.Context) {
190
191
192
	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
193
		return
194
195
	}

Michael Yang's avatar
Michael Yang committed
196
	ch := make(chan any)
Michael Yang's avatar
Michael Yang committed
197
198
	go func() {
		defer close(ch)
199
200
		fn := func(resp api.ProgressResponse) {
			ch <- resp
201
202
		}

203
		if err := CreateModel(req.Name, req.Path, fn); err != nil {
Michael Yang's avatar
Michael Yang committed
204
			ch <- gin.H{"error": err.Error()}
205
		}
Michael Yang's avatar
Michael Yang committed
206
	}()
Michael Yang's avatar
Michael Yang committed
207

Michael Yang's avatar
Michael Yang committed
208
	streamResponse(c, ch)
Bruce MacDonald's avatar
Bruce MacDonald committed
209
210
}

211
212
213
214
215
216
217
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
	}

218
219
220
221
	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 {
222
223
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		}
224
225
		return
	}
226
227
228
}

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

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

313
314
315
316
	r.GET("/", func(c *gin.Context) {
		c.String(http.StatusOK, "Ollama is running")
	})

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

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

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

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

		bts = append(bts, '\n')
		if _, err := w.Write(bts); err != nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
348
			log.Printf("streamResponse: w.Write failed with %s", err)
Michael Yang's avatar
Michael Yang committed
349
350
351
352
353
354
			return false
		}

		return true
	})
}