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

Michael Yang's avatar
Michael Yang committed
16
	"dario.cat/mergo"
Michael Yang's avatar
Michael Yang committed
17
	"github.com/gin-contrib/cors"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
18
19
	"github.com/gin-gonic/gin"

Jeffrey Morgan's avatar
Jeffrey Morgan committed
20
	"github.com/jmorganca/ollama/api"
Michael Yang's avatar
Michael Yang committed
21
	"github.com/jmorganca/ollama/llama"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
22
23
)

24
func GenerateHandler(c *gin.Context) {
25
26
	start := time.Now()

Michael Yang's avatar
Michael Yang committed
27
	var req api.GenerateRequest
Bruce MacDonald's avatar
Bruce MacDonald committed
28
	if err := c.ShouldBindJSON(&req); err != nil {
29
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
Bruce MacDonald's avatar
Bruce MacDonald committed
30
31
		return
	}
32

33
34
35
36
	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
37
	}
Michael Yang's avatar
Michael Yang committed
38

Michael Yang's avatar
Michael Yang committed
39
40
41
42
43
44
45
46
47
48
49
	opts := api.DefaultOptions()
	if err := mergo.Merge(&opts, model.Options, mergo.WithOverride); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	if err := mergo.Merge(&opts, req.Options, mergo.WithOverride); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

50
	prompt, err := model.Prompt(req)
51
52
53
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
Michael Yang's avatar
Michael Yang committed
54
55
	}

Michael Yang's avatar
Michael Yang committed
56
	llm, err := llama.New(model.ModelPath, opts)
Michael Yang's avatar
Michael Yang committed
57
58
59
60
61
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	defer llm.Close()
Jeffrey Morgan's avatar
Jeffrey Morgan committed
62

Michael Yang's avatar
Michael Yang committed
63
64
65
	ch := make(chan any)
	go func() {
		defer close(ch)
Michael Yang's avatar
Michael Yang committed
66
		fn := func(r api.GenerateResponse) {
Michael Yang's avatar
Michael Yang committed
67
68
69
70
71
72
73
			r.Model = req.Model
			r.CreatedAt = time.Now().UTC()
			if r.Done {
				r.TotalDuration = time.Since(start)
			}

			ch <- r
Michael Yang's avatar
Michael Yang committed
74
75
76
77
78
		}

		if err := llm.Predict(req.Context, prompt, fn); err != nil {
			ch <- gin.H{"error": err.Error()}
		}
Michael Yang's avatar
Michael Yang committed
79
	}()
Michael Yang's avatar
Michael Yang committed
80

Michael Yang's avatar
Michael Yang committed
81
	streamResponse(c, ch)
Michael Yang's avatar
Michael Yang committed
82
}
Michael Yang's avatar
Michael Yang committed
83

84
func PullModelHandler(c *gin.Context) {
Michael Yang's avatar
Michael Yang committed
85
86
87
88
89
90
	var req api.PullRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

91
92
93
	ch := make(chan any)
	go func() {
		defer close(ch)
94
95
		fn := func(r api.ProgressResponse) {
			ch <- r
96
		}
97

98
99
100
101
102
103
104
		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
105
			ch <- gin.H{"error": err.Error()}
106
107
108
109
110
111
		}
	}()

	streamResponse(c, ch)
}

112
func PushModelHandler(c *gin.Context) {
113
114
115
	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
116
117
		return
	}
Michael Yang's avatar
Michael Yang committed
118

119
120
121
	ch := make(chan any)
	go func() {
		defer close(ch)
122
123
		fn := func(r api.ProgressResponse) {
			ch <- r
124
		}
125

126
127
128
129
130
131
132
		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
133
			ch <- gin.H{"error": err.Error()}
134
135
136
137
138
139
		}
	}()

	streamResponse(c, ch)
}

140
func CreateModelHandler(c *gin.Context) {
141
142
143
	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
144
		return
145
146
	}

Michael Yang's avatar
Michael Yang committed
147
	ch := make(chan any)
Michael Yang's avatar
Michael Yang committed
148
149
	go func() {
		defer close(ch)
150
151
		fn := func(resp api.ProgressResponse) {
			ch <- resp
152
153
		}

154
		if err := CreateModel(req.Name, req.Path, fn); err != nil {
Michael Yang's avatar
Michael Yang committed
155
			ch <- gin.H{"error": err.Error()}
156
		}
Michael Yang's avatar
Michael Yang committed
157
	}()
Michael Yang's avatar
Michael Yang committed
158

Michael Yang's avatar
Michael Yang committed
159
	streamResponse(c, ch)
Bruce MacDonald's avatar
Bruce MacDonald committed
160
161
}

162
163
164
165
166
167
168
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
	}

169
170
171
172
	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 {
173
174
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		}
175
176
		return
	}
177
178
179
}

func ListModelsHandler(c *gin.Context) {
Patrick Devine's avatar
Patrick Devine committed
180
181
182
183
184
185
186
187
	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 {
188
189
190
191
			if errors.Is(err, os.ErrNotExist) {
				log.Printf("manifest file does not exist: %s", fp)
				return nil
			}
Patrick Devine's avatar
Patrick Devine committed
192
193
194
195
196
			return err
		}
		if !info.IsDir() {
			fi, err := os.Stat(path)
			if err != nil {
197
198
				log.Printf("skipping file: %s", fp)
				return nil
Patrick Devine's avatar
Patrick Devine committed
199
200
201
202
203
204
205
206
207
208
			}
			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 {
209
210
				log.Printf("skipping file: %s", fp)
				return nil
Patrick Devine's avatar
Patrick Devine committed
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
			}
			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
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
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
246
func Serve(ln net.Listener) error {
Michael Yang's avatar
Michael Yang committed
247
248
249
250
251
252
253
254
255
256
257
258
259
260
	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
261
	r := gin.Default()
Michael Yang's avatar
Michael Yang committed
262
	r.Use(cors.New(config))
Bruce MacDonald's avatar
Bruce MacDonald committed
263

264
265
266
267
	r.GET("/", func(c *gin.Context) {
		c.String(http.StatusOK, "Ollama is running")
	})

268
269
270
271
	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
272
	r.POST("/api/copy", CopyModelHandler)
273
274
	r.GET("/api/tags", ListModelsHandler)
	r.DELETE("/api/delete", DeleteModelHandler)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
275
276
277
278
279
280
281
282

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

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

Michael Yang's avatar
Michael Yang committed
284
func streamResponse(c *gin.Context, ch chan any) {
Michael Yang's avatar
Michael Yang committed
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
	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
	})
}