routes.go 6.87 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"
Michael Yang's avatar
Michael Yang committed
13
	"strings"
Michael Yang's avatar
Michael Yang committed
14
	"sync"
15
	"time"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
16

Michael Yang's avatar
Michael Yang committed
17
	"dario.cat/mergo"
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
)

Michael Yang's avatar
Michael Yang committed
25
26
27
28
29
30
31
var mu sync.Mutex

var activeSession struct {
	ID int64
	*llama.LLM
}

32
func GenerateHandler(c *gin.Context) {
Michael Yang's avatar
Michael Yang committed
33
34
35
	mu.Lock()
	defer mu.Unlock()

Michael Yang's avatar
Michael Yang committed
36
	checkpointStart := time.Now()
37

Michael Yang's avatar
Michael Yang committed
38
	var req api.GenerateRequest
Bruce MacDonald's avatar
Bruce MacDonald committed
39
	if err := c.ShouldBindJSON(&req); err != nil {
40
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
Bruce MacDonald's avatar
Bruce MacDonald committed
41
42
		return
	}
43

44
45
46
47
	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
48
	}
Michael Yang's avatar
Michael Yang committed
49

Michael Yang's avatar
Michael Yang committed
50
51
52
53
54
	if req.SessionID == 0 || req.SessionID != activeSession.ID {
		if activeSession.LLM != nil {
			activeSession.Close()
			activeSession.LLM = nil
		}
Michael Yang's avatar
Michael Yang committed
55

Michael Yang's avatar
Michael Yang committed
56
57
58
59
60
		opts := api.DefaultOptions()
		if err := mergo.Merge(&opts, model.Options, mergo.WithOverride); err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}
Michael Yang's avatar
Michael Yang committed
61

Michael Yang's avatar
Michael Yang committed
62
63
64
65
66
67
68
69
70
71
72
73
74
		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
		}

		activeSession.ID = time.Now().UnixNano()
		activeSession.LLM = llm
Michael Yang's avatar
Michael Yang committed
75
76
	}

Michael Yang's avatar
Michael Yang committed
77
78
	checkpointLoaded := time.Now()

Michael Yang's avatar
Michael Yang committed
79
	prompt, err := model.Prompt(req)
Michael Yang's avatar
Michael Yang committed
80
81
82
83
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
84

Michael Yang's avatar
Michael Yang committed
85
86
87
	ch := make(chan any)
	go func() {
		defer close(ch)
Michael Yang's avatar
Michael Yang committed
88
		fn := func(r api.GenerateResponse) {
Michael Yang's avatar
Michael Yang committed
89
90
			r.Model = req.Model
			r.CreatedAt = time.Now().UTC()
Michael Yang's avatar
Michael Yang committed
91
			r.SessionID = activeSession.ID
Michael Yang's avatar
Michael Yang committed
92
			if r.Done {
Michael Yang's avatar
Michael Yang committed
93
94
				r.TotalDuration = time.Since(checkpointStart)
				r.LoadDuration = checkpointLoaded.Sub(checkpointStart)
Michael Yang's avatar
Michael Yang committed
95
96
97
			}

			ch <- r
Michael Yang's avatar
Michael Yang committed
98
99
		}

Michael Yang's avatar
Michael Yang committed
100
		if err := activeSession.LLM.Predict(req.Context, prompt, fn); err != nil {
Michael Yang's avatar
Michael Yang committed
101
102
			ch <- gin.H{"error": err.Error()}
		}
Michael Yang's avatar
Michael Yang committed
103
	}()
Michael Yang's avatar
Michael Yang committed
104

Michael Yang's avatar
Michael Yang committed
105
	streamResponse(c, ch)
Michael Yang's avatar
Michael Yang committed
106
}
Michael Yang's avatar
Michael Yang committed
107

108
func PullModelHandler(c *gin.Context) {
Michael Yang's avatar
Michael Yang committed
109
110
111
112
113
114
	var req api.PullRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

115
116
117
	ch := make(chan any)
	go func() {
		defer close(ch)
118
119
		fn := func(r api.ProgressResponse) {
			ch <- r
120
		}
121

122
123
124
125
126
127
128
		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
129
			ch <- gin.H{"error": err.Error()}
130
131
132
133
134
135
		}
	}()

	streamResponse(c, ch)
}

136
func PushModelHandler(c *gin.Context) {
137
138
139
	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
140
141
		return
	}
Michael Yang's avatar
Michael Yang committed
142

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

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

	streamResponse(c, ch)
}

164
func CreateModelHandler(c *gin.Context) {
165
166
167
	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
168
		return
169
170
	}

Michael Yang's avatar
Michael Yang committed
171
	ch := make(chan any)
Michael Yang's avatar
Michael Yang committed
172
173
	go func() {
		defer close(ch)
174
175
		fn := func(resp api.ProgressResponse) {
			ch <- resp
176
177
		}

178
		if err := CreateModel(req.Name, req.Path, fn); err != nil {
Michael Yang's avatar
Michael Yang committed
179
			ch <- gin.H{"error": err.Error()}
180
		}
Michael Yang's avatar
Michael Yang committed
181
	}()
Michael Yang's avatar
Michael Yang committed
182

Michael Yang's avatar
Michael Yang committed
183
	streamResponse(c, ch)
Bruce MacDonald's avatar
Bruce MacDonald committed
184
185
}

186
187
188
189
190
191
192
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
	}

193
194
195
196
	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 {
197
198
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		}
199
200
		return
	}
201
202
203
}

func ListModelsHandler(c *gin.Context) {
Patrick Devine's avatar
Patrick Devine committed
204
205
206
207
208
209
210
211
	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 {
212
213
214
215
			if errors.Is(err, os.ErrNotExist) {
				log.Printf("manifest file does not exist: %s", fp)
				return nil
			}
Patrick Devine's avatar
Patrick Devine committed
216
217
218
219
220
			return err
		}
		if !info.IsDir() {
			fi, err := os.Stat(path)
			if err != nil {
221
222
				log.Printf("skipping file: %s", fp)
				return nil
Patrick Devine's avatar
Patrick Devine committed
223
224
225
226
227
228
229
230
231
232
			}
			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 {
233
234
				log.Printf("skipping file: %s", fp)
				return nil
Patrick Devine's avatar
Patrick Devine committed
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
			}
			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
	}

	c.JSON(http.StatusOK, api.ListResponse{models})
}

Patrick Devine's avatar
Patrick Devine committed
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
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
270
func Serve(ln net.Listener) error {
Michael Yang's avatar
Michael Yang committed
271
272
273
274
275
276
277
278
279
280
281
282
283
284
	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
285
	r := gin.Default()
Michael Yang's avatar
Michael Yang committed
286
	r.Use(cors.New(config))
Bruce MacDonald's avatar
Bruce MacDonald committed
287

288
289
290
291
	r.GET("/", func(c *gin.Context) {
		c.String(http.StatusOK, "Ollama is running")
	})

292
293
294
295
	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
296
	r.POST("/api/copy", CopyModelHandler)
297
298
	r.GET("/api/tags", ListModelsHandler)
	r.DELETE("/api/delete", DeleteModelHandler)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
299
300
301
302
303
304
305
306

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

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

Michael Yang's avatar
Michael Yang committed
308
func streamResponse(c *gin.Context, ch chan any) {
Michael Yang's avatar
Michael Yang committed
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
	c.Stream(func(w io.Writer) bool {
		val, ok := <-ch
		if !ok {
			return false
		}

		bts, err := json.Marshal(val)
		if err != nil {
			return false
		}

		bts = append(bts, '\n')
		if _, err := w.Write(bts); err != nil {
			return false
		}

		return true
	})
}