routes.go 3.99 KB
Newer Older
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1
2
3
package server

import (
Michael Yang's avatar
Michael Yang committed
4
	"embed"
Michael Yang's avatar
Michael Yang committed
5
	"encoding/json"
6
	"errors"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
7
8
	"io"
	"log"
Michael Yang's avatar
Michael Yang committed
9
	"math"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
10
11
	"net"
	"net/http"
12
	"os"
Michael Yang's avatar
Michael Yang committed
13
14
15
	"path"
	"strings"
	"text/template"
16
	"time"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
17
18

	"github.com/gin-gonic/gin"
Michael Yang's avatar
Michael Yang committed
19
	"github.com/lithammer/fuzzysearch/fuzzy"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
20

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
//go:embed templates/*
var templatesFS embed.FS
var templates = template.Must(template.ParseFS(templatesFS, "templates/*.prompt"))
Michael Yang's avatar
Michael Yang committed
28

29
30
31
32
33
34
35
36
37
func cacheDir() string {
	home, err := os.UserHomeDir()
	if err != nil {
		panic(err)
	}

	return path.Join(home, ".ollama")
}

Bruce MacDonald's avatar
Bruce MacDonald committed
38
func generate(c *gin.Context) {
39
40
	start := time.Now()

Michael Yang's avatar
Michael Yang committed
41
42
	req := api.GenerateRequest{
		Options: api.DefaultOptions(),
43
44
	}

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

Bruce MacDonald's avatar
Bruce MacDonald committed
50
	if remoteModel, _ := getRemote(req.Model); remoteModel != nil {
Michael Yang's avatar
Michael Yang committed
51
52
		req.Model = remoteModel.FullName()
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
53
54
	if _, err := os.Stat(req.Model); err != nil {
		if !errors.Is(err, os.ErrNotExist) {
55
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
Bruce MacDonald's avatar
Bruce MacDonald committed
56
57
58
59
			return
		}
		req.Model = path.Join(cacheDir(), "models", req.Model+".bin")
	}
Michael Yang's avatar
Michael Yang committed
60

Michael Yang's avatar
Michael Yang committed
61
62
	ch := make(chan any)
	go stream(c, ch)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
63

Michael Yang's avatar
Michael Yang committed
64
65
66
67
68
	templateNames := make([]string, 0, len(templates.Templates()))
	for _, template := range templates.Templates() {
		templateNames = append(templateNames, template.Name())
	}

Michael Yang's avatar
Michael Yang committed
69
	match, _ := matchRankOne(path.Base(req.Model), templateNames)
Michael Yang's avatar
Michael Yang committed
70
71
72
	if template := templates.Lookup(match); template != nil {
		var sb strings.Builder
		if err := template.Execute(&sb, req); err != nil {
73
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
Michael Yang's avatar
Michael Yang committed
74
75
76
77
78
79
			return
		}

		req.Prompt = sb.String()
	}

Michael Yang's avatar
Michael Yang committed
80
81
82
83
84
85
	llm, err := llama.New(req.Model, req.Options)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	defer llm.Close()
Jeffrey Morgan's avatar
Jeffrey Morgan committed
86

87
88
89
90
91
92
93
94
	fn := func(r api.GenerateResponse) {
		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
95
	}
Michael Yang's avatar
Michael Yang committed
96

Michael Yang's avatar
Michael Yang committed
97
	if err := llm.Predict(req.Prompt, fn); err != nil {
Michael Yang's avatar
Michael Yang committed
98
99
100
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
Michael Yang's avatar
Michael Yang committed
101

Michael Yang's avatar
Michael Yang committed
102
}
Michael Yang's avatar
Michael Yang committed
103

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

	remote, err := getRemote(req.Model)
	if err != nil {
Michael Yang's avatar
Michael Yang committed
113
		c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
Michael Yang's avatar
Michael Yang committed
114
115
		return
	}
Michael Yang's avatar
Michael Yang committed
116

Michael Yang's avatar
Michael Yang committed
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
	// check if completed file exists
	fi, err := os.Stat(remote.FullName())
	switch {
	case errors.Is(err, os.ErrNotExist):
		// noop, file doesn't exist so create it
	case err != nil:
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	default:
		c.JSON(http.StatusOK, api.PullProgress{
			Total:     fi.Size(),
			Completed: fi.Size(),
			Percent:   100,
		})

		return
	}

Michael Yang's avatar
Michael Yang committed
135
	ch := make(chan any)
Michael Yang's avatar
Michael Yang committed
136
	go stream(c, ch)
Michael Yang's avatar
Michael Yang committed
137

Michael Yang's avatar
Michael Yang committed
138
139
140
141
	fn := func(total, completed int64) {
		ch <- api.PullProgress{
			Total:     total,
			Completed: completed,
Michael Yang's avatar
Michael Yang committed
142
			Percent:   float64(completed) / float64(total) * 100,
Michael Yang's avatar
Michael Yang committed
143
144
		}
	}
Michael Yang's avatar
Michael Yang committed
145

Michael Yang's avatar
Michael Yang committed
146
	if err := saveModel(remote, fn); err != nil {
Michael Yang's avatar
Michael Yang committed
147
148
149
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
150
151
152
153
154
}

func Serve(ln net.Listener) error {
	r := gin.Default()

155
156
157
158
	r.GET("/", func(c *gin.Context) {
		c.String(http.StatusOK, "Ollama is running")
	})

Michael Yang's avatar
Michael Yang committed
159
	r.POST("/api/pull", pull)
Bruce MacDonald's avatar
Bruce MacDonald committed
160
	r.POST("/api/generate", generate)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
161
162
163
164
165
166
167
168

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

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

func matchRankOne(source string, targets []string) (bestMatch string, bestRank int) {
Michael Yang's avatar
Michael Yang committed
171
	bestRank = math.MaxInt
Michael Yang's avatar
Michael Yang committed
172
	for _, target := range targets {
Michael Yang's avatar
Michael Yang committed
173
		if rank := fuzzy.LevenshteinDistance(source, target); bestRank > rank {
Michael Yang's avatar
Michael Yang committed
174
175
176
177
178
179
180
			bestRank = rank
			bestMatch = target
		}
	}

	return
}
Michael Yang's avatar
Michael Yang committed
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201

func stream(c *gin.Context, ch chan any) {
	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
	})
}