Skip to content

Commit

Permalink
Merge pull request #33 from grafana/vector-service
Browse files Browse the repository at this point in the history
Add vector service concept, and resource endpoint to handle vector search
  • Loading branch information
sd2k authored Sep 14, 2023
2 parents fc950da + bbfa61c commit b605e7b
Show file tree
Hide file tree
Showing 15 changed files with 668 additions and 29 deletions.
8 changes: 7 additions & 1 deletion cspell.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,20 @@
"AIURL",
"datasource",
"datasources",
"embedder",
"errcheck",
"eventsource",
"grafana",
"httpadapter",
"instancemgmt",
"llms",
"nolint",
"openai",
"proxying",
"qdrant",
"testid",
"unmarshalling"
"unmarshalling",
"Upsert",
"vectorapi"
]
}
22 changes: 21 additions & 1 deletion docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,29 @@ services:
build:
context: ./.config
args:
grafana_version: ${GRAFANA_VERSION:-9.5.2}
grafana_version: ${GRAFANA_VERSION:-10.1.0}
environment:
OPENAI_API_KEY: $OPENAI_API_KEY
ports:
- 3000:3000/tcp
volumes:
- ./dist:/var/lib/grafana/plugins/grafana-llm-app
- ./provisioning:/etc/grafana/provisioning

qdrant:
image: qdrant/qdrant
volumes:
- qdrant-storage:/qdrant/storage

# vectorapi:
# image: vectorapi:latest
# environment:
# PORT: 8889
# ports:
# - 8889:8889
# volumes:
# - sentence-transformers:/app/.sentence_transfomers

volumes:
# sentence-transformers:
qdrant-storage:
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ go 1.19
require (
github.com/grafana/grafana-plugin-sdk-go v0.175.0
github.com/launchdarkly/eventsource v1.7.1
github.com/qdrant/go-client v1.5.0
google.golang.org/grpc v1.57.0
)

require (
Expand Down Expand Up @@ -80,7 +82,6 @@ require (
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230731193218-e0aa005b6bdf // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230731193218-e0aa005b6bdf // indirect
google.golang.org/grpc v1.57.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
github.com/qdrant/go-client v1.5.0 h1:JxIp4oTFqZqsX7K5LZbfr2fCgfiT/mRsFaxJ1qCpdVM=
github.com/qdrant/go-client v1.5.0/go.mod h1:680gkxNAsVtre0Z8hAQmtPzJtz1xFAyCu2TUxULtnoE=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
Expand Down
1 change: 1 addition & 0 deletions pkg/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
)

func main() {
log.DefaultLogger.Info("Starting plugin process")
// Start listening to requests sent from Grafana. This call is blocking so
// it won't finish until Grafana shuts down the process or the plugin choose
// to exit by itself using os.Exit. Manage automatically manages life cycle
Expand Down
51 changes: 25 additions & 26 deletions pkg/plugin/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ package plugin

import (
"context"
"encoding/json"
"net/http"

"github.com/grafana/grafana-llm-app/pkg/plugin/vector"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
"github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter"
)

Expand All @@ -21,37 +22,16 @@ var (
_ backend.StreamHandler = (*App)(nil)
)

const openAIKey = "openAIKey"

type OpenAISettings struct {
URL string `json:"url"`
OrganizationID string `json:"organizationId"`
apiKey string
}

type Settings struct {
OpenAI OpenAISettings `json:"openAI"`
}

func loadSettings(appSettings backend.AppInstanceSettings) Settings {
settings := Settings{
OpenAI: OpenAISettings{
URL: "https://api.openai.com",
},
}
_ = json.Unmarshal(appSettings.JSONData, &settings)

settings.OpenAI.apiKey = appSettings.DecryptedSecureJSONData[openAIKey]
return settings
}

// App is an example app backend plugin which can respond to data queries.
type App struct {
backend.CallResourceHandler

vectorService vector.Service
}

// NewApp creates a new example *App instance.
func NewApp(appSettings backend.AppInstanceSettings) (instancemgmt.Instance, error) {
log.DefaultLogger.Debug("Creating new app instance")
var app App

// Use a httpadapter (provided by the SDK) for resource calls. This allows us
Expand All @@ -61,17 +41,36 @@ func NewApp(appSettings backend.AppInstanceSettings) (instancemgmt.Instance, err
app.registerRoutes(mux)
app.CallResourceHandler = httpadapter.New(mux)

log.DefaultLogger.Debug("Loading settings")
settings := loadSettings(appSettings)
var err error

if settings.Vector.Enabled {
log.DefaultLogger.Debug("Creating vector service")
app.vectorService, err = vector.NewService(
settings.Vector,
appSettings.DecryptedSecureJSONData,
)
if err != nil {
log.DefaultLogger.Error("Error creating vector service", "err", err)
return nil, err
}
}

return &app, nil
}

// Dispose here tells plugin SDK that plugin wants to clean up resources when a new instance
// created.
func (a *App) Dispose() {
// cleanup
if a.vectorService != nil {
a.vectorService.Cancel()
}
}

// CheckHealth handles health checks sent from Grafana to the plugin.
func (a *App) CheckHealth(_ context.Context, _ *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
log.DefaultLogger.Info("check health")
return &backend.CheckHealthResult{
Status: backend.HealthStatusOk,
Message: "ok",
Expand Down
44 changes: 44 additions & 0 deletions pkg/plugin/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net/url"
"strings"

"github.com/grafana/grafana-llm-app/pkg/plugin/vector/store"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
"github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter"
)
Expand Down Expand Up @@ -62,9 +63,52 @@ func newOpenAIProxy() http.Handler {
}
}

type vectorSearchRequest struct {
Text string `json:"text"`
Collection string `json:"collection"`
Limit uint64 `json:"limit"`
}

type vectorSearchResponse struct {
Results []store.SearchResult `json:"results"`
}

func (app *App) handleVectorSearch(w http.ResponseWriter, req *http.Request) {
if app.vectorService == nil {
w.WriteHeader(http.StatusServiceUnavailable)
return
}
if req.Method != http.MethodPost {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
body := vectorSearchRequest{}
if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if body.Limit == 0 {
body.Limit = 10
}
results, err := app.vectorService.Search(req.Context(), body.Collection, body.Text, body.Limit)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
resp := vectorSearchResponse{Results: results}
bodyJSON, err := json.Marshal(resp)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
//nolint:errcheck // Just do our best to write.
w.Write(bodyJSON)
}

// registerRoutes takes a *http.ServeMux and registers some HTTP handlers.
func (a *App) registerRoutes(mux *http.ServeMux) {
mux.HandleFunc("/ping", a.handlePing)
mux.HandleFunc("/echo", a.handleEcho)
mux.Handle("/openai/", newOpenAIProxy())
mux.HandleFunc("/vector/search", a.handleVectorSearch)
}
36 changes: 36 additions & 0 deletions pkg/plugin/settings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package plugin

import (
"encoding/json"

"github.com/grafana/grafana-llm-app/pkg/plugin/vector"
"github.com/grafana/grafana-plugin-sdk-go/backend"
)

const openAIKey = "openAIKey"

type OpenAISettings struct {
URL string `json:"url"`
OrganizationID string `json:"organizationId"`
apiKey string
}

type Settings struct {
OpenAI OpenAISettings `json:"openAI"`

openAIKey string

Vector vector.VectorSettings `json:"vector"`
}

func loadSettings(appSettings backend.AppInstanceSettings) Settings {
settings := Settings{
OpenAI: OpenAISettings{
URL: "https://api.openai.com",
},
}
_ = json.Unmarshal(appSettings.JSONData, &settings)

settings.openAIKey = appSettings.DecryptedSecureJSONData[openAIKey]
return settings
}
33 changes: 33 additions & 0 deletions pkg/plugin/vector/embed/embedder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package embed

import (
"context"

"github.com/grafana/grafana-plugin-sdk-go/backend/log"
)

type EmbedderType string

const (
EmbedderOpenAI EmbedderType = "openai"
)

type Embedder interface {
Embed(ctx context.Context, model string, text string) ([]float32, error)
}

type Settings struct {
Type string `json:"type"`

OpenAI openAISettings `json:"openai"`
}

// NewEmbedder creates a new embedder.
func NewEmbedder(s Settings, secrets map[string]string) (Embedder, error) {
switch EmbedderType(s.Type) {
case EmbedderOpenAI:
log.DefaultLogger.Debug("Creating OpenAI embedder")
return newOpenAIEmbedder(s.OpenAI, secrets), nil
}
return nil, nil
}
Loading

0 comments on commit b605e7b

Please sign in to comment.