routes.go 3.48 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
	ch := make(chan any)
Michael Yang's avatar
Michael Yang committed
109
	go stream(c, ch)
Michael Yang's avatar
Michael Yang committed
110

Michael Yang's avatar
Michael Yang committed
111
112
113
114
	fn := func(total, completed int64) {
		ch <- api.PullProgress{
			Total:     total,
			Completed: completed,
Michael Yang's avatar
Michael Yang committed
115
			Percent:   float64(completed) / float64(total) * 100,
Michael Yang's avatar
Michael Yang committed
116
117
		}
	}
Michael Yang's avatar
Michael Yang committed
118

Michael Yang's avatar
Michael Yang committed
119
	if err := saveModel(remote, fn); err != nil {
Michael Yang's avatar
Michael Yang committed
120
121
122
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
123
124
125
126
127
}

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

128
129
130
131
	r.GET("/", func(c *gin.Context) {
		c.String(http.StatusOK, "Ollama is running")
	})

Michael Yang's avatar
Michael Yang committed
132
	r.POST("api/pull", pull)
Bruce MacDonald's avatar
Bruce MacDonald committed
133
	r.POST("/api/generate", generate)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
134
135
136
137
138
139
140
141

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

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

func matchRankOne(source string, targets []string) (bestMatch string, bestRank int) {
Michael Yang's avatar
Michael Yang committed
144
	bestRank = math.MaxInt
Michael Yang's avatar
Michael Yang committed
145
	for _, target := range targets {
Michael Yang's avatar
Michael Yang committed
146
		if rank := fuzzy.LevenshteinDistance(source, target); bestRank > rank {
Michael Yang's avatar
Michael Yang committed
147
148
149
150
151
152
153
			bestRank = rank
			bestMatch = target
		}
	}

	return
}
Michael Yang's avatar
Michael Yang committed
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174

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