routes.go 3.88 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
63
64
65
	templateNames := make([]string, 0, len(templates.Templates()))
	for _, template := range templates.Templates() {
		templateNames = append(templateNames, template.Name())
	}

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

		req.Prompt = sb.String()
	}

Michael Yang's avatar
Michael Yang committed
77
78
79
80
81
82
	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
83

Michael Yang's avatar
Michael Yang committed
84
85
86
87
88
89
90
91
92
93
94
95
96
	ch := make(chan any)
	go func() {
		defer close(ch)
		llm.Predict(req.Context, req.Prompt, 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
97

Michael Yang's avatar
Michael Yang committed
98
	streamResponse(c, ch)
Michael Yang's avatar
Michael Yang committed
99
}
Michael Yang's avatar
Michael Yang committed
100

Michael Yang's avatar
Michael Yang committed
101
102
103
104
105
106
107
108
109
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
110
		c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
Michael Yang's avatar
Michael Yang committed
111
112
		return
	}
Michael Yang's avatar
Michael Yang committed
113

Michael Yang's avatar
Michael Yang committed
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
	// 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
132
	ch := make(chan any)
Michael Yang's avatar
Michael Yang committed
133
134
135
136
137
138
139
140
141
142
	go func() {
		defer close(ch)
		saveModel(remote, func(total, completed int64) {
			ch <- api.PullProgress{
				Total:     total,
				Completed: completed,
				Percent:   float64(completed) / float64(total) * 100,
			}
		})
	}()
Michael Yang's avatar
Michael Yang committed
143

Michael Yang's avatar
Michael Yang committed
144
	streamResponse(c, ch)
Bruce MacDonald's avatar
Bruce MacDonald committed
145
146
147
148
149
}

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

150
151
152
153
	r.GET("/", func(c *gin.Context) {
		c.String(http.StatusOK, "Ollama is running")
	})

Michael Yang's avatar
Michael Yang committed
154
	r.POST("/api/pull", pull)
Bruce MacDonald's avatar
Bruce MacDonald committed
155
	r.POST("/api/generate", generate)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
156
157
158
159
160
161
162
163

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

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

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

	return
}
Michael Yang's avatar
Michael Yang committed
176

Michael Yang's avatar
Michael Yang committed
177
func streamResponse(c *gin.Context, ch chan any) {
Michael Yang's avatar
Michael Yang committed
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
	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
	})
}