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
115
116
117
	fn := func(total, completed int64) {
		ch <- api.PullProgress{
			Total:     total,
			Completed: completed,
			Percent:   float64(total) / float64(completed) * 100,
		}
	}
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
	})
}