routes.go 6.76 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()

36
37
	start := time.Now()

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
	prompt, err := model.Prompt(req)
Michael Yang's avatar
Michael Yang committed
78
79
80
81
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
82

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

			ch <- r
Michael Yang's avatar
Michael Yang committed
95
96
		}

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

Michael Yang's avatar
Michael Yang committed
102
	streamResponse(c, ch)
Michael Yang's avatar
Michael Yang committed
103
}
Michael Yang's avatar
Michael Yang committed
104

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

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

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

	streamResponse(c, ch)
}

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

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 := PushModel(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 CreateModelHandler(c *gin.Context) {
162
163
164
	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
165
		return
166
167
	}

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

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

Michael Yang's avatar
Michael Yang committed
180
	streamResponse(c, ch)
Bruce MacDonald's avatar
Bruce MacDonald committed
181
182
}

183
184
185
186
187
188
189
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
	}

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

func ListModelsHandler(c *gin.Context) {
Patrick Devine's avatar
Patrick Devine committed
201
202
203
204
205
206
207
208
	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 {
209
210
211
212
			if errors.Is(err, os.ErrNotExist) {
				log.Printf("manifest file does not exist: %s", fp)
				return nil
			}
Patrick Devine's avatar
Patrick Devine committed
213
214
215
216
217
			return err
		}
		if !info.IsDir() {
			fi, err := os.Stat(path)
			if err != nil {
218
219
				log.Printf("skipping file: %s", fp)
				return nil
Patrick Devine's avatar
Patrick Devine committed
220
221
222
223
224
225
226
227
228
229
			}
			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 {
230
231
				log.Printf("skipping file: %s", fp)
				return nil
Patrick Devine's avatar
Patrick Devine committed
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
			}
			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
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
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
267
func Serve(ln net.Listener) error {
Michael Yang's avatar
Michael Yang committed
268
269
270
271
272
273
274
275
276
277
278
279
280
281
	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
282
	r := gin.Default()
Michael Yang's avatar
Michael Yang committed
283
	r.Use(cors.New(config))
Bruce MacDonald's avatar
Bruce MacDonald committed
284

285
286
287
288
	r.GET("/", func(c *gin.Context) {
		c.String(http.StatusOK, "Ollama is running")
	})

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

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

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

Michael Yang's avatar
Michael Yang committed
305
func streamResponse(c *gin.Context, ch chan any) {
Michael Yang's avatar
Michael Yang committed
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
	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
	})
}