routes.go 3.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
	"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"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
16
17

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

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
)

Michael Yang's avatar
Michael Yang committed
24
25
26
//go:embed templates/*
var templatesFS embed.FS
var templates = template.Must(template.ParseFS(templatesFS, "templates/*.prompt"))
Michael Yang's avatar
Michael Yang committed
27

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

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

Bruce MacDonald's avatar
Bruce MacDonald committed
37
func generate(c *gin.Context) {
Michael Yang's avatar
Michael Yang committed
38
39
	req := api.GenerateRequest{
		Options: api.DefaultOptions(),
40
41
	}

Bruce MacDonald's avatar
Bruce MacDonald committed
42
	if err := c.ShouldBindJSON(&req); err != nil {
43
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
Bruce MacDonald's avatar
Bruce MacDonald committed
44
45
		return
	}
46

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

Michael Yang's avatar
Michael Yang committed
58
59
	ch := make(chan any)
	go stream(c, ch)
Jeffrey Morgan's avatar
Jeffrey Morgan 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
	fn := func(s string) {
		ch <- api.GenerateResponse{Response: s}
	}
Michael Yang's avatar
Michael Yang committed
87

Michael Yang's avatar
Michael Yang committed
88
	if err := llm.Predict(req.Prompt, fn); err != nil {
Michael Yang's avatar
Michael Yang committed
89
90
91
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
Michael Yang's avatar
Michael Yang committed
92

Michael Yang's avatar
Michael Yang committed
93
}
Michael Yang's avatar
Michael Yang committed
94

Michael Yang's avatar
Michael Yang committed
95
96
97
98
99
100
101
102
103
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
104
		c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
Michael Yang's avatar
Michael Yang committed
105
106
		return
	}
Michael Yang's avatar
Michael Yang committed
107

Michael Yang's avatar
Michael Yang committed
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
	// 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
126
	ch := make(chan any)
Michael Yang's avatar
Michael Yang committed
127
	go stream(c, ch)
Michael Yang's avatar
Michael Yang committed
128

Michael Yang's avatar
Michael Yang committed
129
130
131
132
	fn := func(total, completed int64) {
		ch <- api.PullProgress{
			Total:     total,
			Completed: completed,
Michael Yang's avatar
Michael Yang committed
133
			Percent:   float64(completed) / float64(total) * 100,
Michael Yang's avatar
Michael Yang committed
134
135
		}
	}
Michael Yang's avatar
Michael Yang committed
136

Michael Yang's avatar
Michael Yang committed
137
	if err := saveModel(remote, fn); err != nil {
Michael Yang's avatar
Michael Yang committed
138
139
140
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
141
142
143
144
145
}

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

146
147
148
149
	r.GET("/", func(c *gin.Context) {
		c.String(http.StatusOK, "Ollama is running")
	})

Michael Yang's avatar
Michael Yang committed
150
	r.POST("api/pull", pull)
Bruce MacDonald's avatar
Bruce MacDonald committed
151
	r.POST("/api/generate", generate)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
152
153
154
155
156
157
158
159

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

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

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

	return
}
Michael Yang's avatar
Michael Yang committed
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192

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
	})
}