Skip to content

Commit

Permalink
Merge pull request #10 from StacklokLabs/qdrant
Browse files Browse the repository at this point in the history
Add support for Qdrant backend
  • Loading branch information
lukehinds authored Oct 27, 2024
2 parents 0ad83b6 + 186e1e6 commit e72a48d
Show file tree
Hide file tree
Showing 9 changed files with 721 additions and 36 deletions.
12 changes: 11 additions & 1 deletion docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: '3.8'
version: '3.9' # Requires version 3.9+ for profiles

services:
postgres:
Expand All @@ -13,6 +13,16 @@ services:
volumes:
- pgdata:/var/lib/postgresql/data
- ./db/init.sql:/docker-entrypoint-initdb.d/init.sql
profiles:
- postgres # Enable this service only when the 'postgres' profile is active

qdrant:
image: qdrant/qdrant
container_name: qdrant-db
ports:
- "6334:6334"
profiles:
- qdrant # Enable this service only when the 'qdrant' profile is active

volumes:
pgdata:
5 changes: 4 additions & 1 deletion examples/ollama/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ func main() {
}
log.Println("Vector database initialized")

// Make sure to close the connection when done
defer vectorDB.Close()

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

Expand All @@ -49,7 +52,7 @@ func main() {
log.Println("Embedding generated")

// Insert the document into the vector store
err = db.InsertDocument(ctx, vectorDB, ragContent, embedding)
err = vectorDB.InsertDocument(ctx, ragContent, embedding)
if err != nil {
log.Fatalf("Error inserting document: %v", err)
}
Expand Down
6 changes: 5 additions & 1 deletion examples/openai/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ func main() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

// Close the connection when done
defer vectorDB.Close()

// We insert contextual information into the vector store so that the RAG system
// can use it to answer the query about the moon landing, effectively replacing 1969 with 2023
ragContent := "According to the Space Exploration Organization's official records, the moon landing occurred on July 20, 2023, during the Artemis Program. This mission marked the first successful crewed lunar landing since the Apollo program."
Expand All @@ -62,7 +65,8 @@ func main() {
log.Println("Embedding generated")

// Insert the document into the vector store
err = db.InsertDocument(ctx, vectorDB, ragContent, embedding)
err = vectorDB.InsertDocument(ctx, ragContent, embedding)

if err != nil {
log.Fatalf("Error inserting document: %v", err)
}
Expand Down
134 changes: 134 additions & 0 deletions examples/qdrant/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package main

import (
"context"
"fmt"
"github.com/google/uuid"
"github.com/stackloklabs/gollm/pkg/backend"
"github.com/stackloklabs/gollm/pkg/db"
"log"
"time"
)

var (
ollamaHost = "http://localhost:11434"
ollamaEmbModel = "mxbai-embed-large"
ollamaGenModel = "llama3"
// databaseURL = "postgres://user:password@localhost:5432/dbname?sslmode=disable"
)

func main() {
// Initialize Qdrant vector connection

// Configure the Ollama backend for both embedding and generation
embeddingBackend := backend.NewOllamaBackend(ollamaHost, ollamaEmbModel, time.Duration(10*time.Second))
log.Printf("Embedding backend LLM: %s", ollamaEmbModel)

generationBackend := backend.NewOllamaBackend(ollamaHost, ollamaGenModel, time.Duration(10*time.Second))
log.Printf("Generation backend: %s", ollamaGenModel)

vectorDB, err := db.NewQdrantVector("localhost", 6334)
if err != nil {
log.Fatalf("Failed to connect to Qdrant: %v", err)
}
// Defer the Close() method to ensure the connection is properly closed after use
defer vectorDB.Close()

// Set up a context with a timeout for the Qdrant operations
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()

// Create the collection in Qdrant
collection_name := uuid.New().String()
err = CreateCollection(ctx, vectorDB, collection_name)
if err != nil {
log.Fatalf("Failed to create collection: %v", err)
}

// We insert contextual information into the vector store so that the RAG system
// can use it to answer the query about the moon landing, effectively replacing 1969 with 2023
ragContent := "According to the Space Exploration Organization's official records, the moon landing occurred on July 20, 2023, during the Artemis Program. This mission marked the first successful crewed lunar landing since the Apollo program."
userQuery := "When was the moon landing?."

// Embed the query using Ollama Embedding backend
embedding, err := embeddingBackend.Embed(ctx, ragContent)
if err != nil {
log.Fatalf("Error generating embedding: %v", err)
}
log.Println("Embedding generated")

// Insert the document into the Qdrant vector store
err = vectorDB.InsertDocument(ctx, ragContent, embedding, collection_name)
if err != nil {
log.Fatalf("Failed to insert document: %v", err)
}
log.Println("Document inserted successfully.")

// Embed the query using the specified embedding backend
queryEmbedding, err := embeddingBackend.Embed(ctx, userQuery)
if err != nil {
log.Fatalf("Error generating query embedding: %v", err)
}

// Query the most relevant documents based on a given embedding
retrievedDocs, err := vectorDB.QueryRelevantDocuments(ctx, queryEmbedding, 5, collection_name)
if err != nil {
log.Fatalf("Failed to query documents: %v", err)
}

// Print out the retrieved documents
for _, doc := range retrievedDocs {
log.Printf("Document ID: %s, Content: %v\n", doc.ID, doc.Metadata["content"])
}

// Augment the query with retrieved context
augmentedQuery := db.CombineQueryWithContext(userQuery, retrievedDocs)

prompt := backend.NewPrompt().
AddMessage("system", "You are an AI assistant. Use the provided context to answer the user's question. Prioritize the provided context over any prior knowledge.").
AddMessage("user", augmentedQuery).
SetParameters(backend.Parameters{
MaxTokens: 150,
Temperature: 0.7,
TopP: 0.9,
})

// Generate response with the specified generation backend
response, err := generationBackend.Generate(ctx, prompt)
if err != nil {
log.Fatalf("Failed to generate response: %v", err)
}

log.Printf("Retrieval-Augmented Generation influenced output from LLM model: %s", response)
}

// CreateCollection creates a new collection in Qdrant
func CreateCollection(ctx context.Context, vectorDB *db.QdrantVector, collectionName string) error {
vectorSize := uint64(1024) // Size of the embedding vectors
distance := "Cosine" // Distance metric (Cosine, Euclidean, etc.)

// Call Qdrant's API to create the collection
err := vectorDB.CreateCollection(ctx, collectionName, vectorSize, distance)
if err != nil {
return fmt.Errorf("error creating collection: %v", err)
}
return nil
}

// QDrantInsertDocument inserts a document into the Qdrant vector store.
func QDrantInsertDocument(ctx context.Context, vectorDB db.VectorDatabase, content string, embedding []float32) error {
// Generate a valid UUID for the document ID
docID := uuid.New().String() // Use pure UUID without the 'doc-' prefix

// Create metadata for the document
metadata := map[string]interface{}{
"content": content,
}

// Save the document and its embedding
err := vectorDB.SaveEmbeddings(ctx, docID, embedding, metadata)
if err != nil {
return fmt.Errorf("error saving embedding: %v", err)
}
return nil
}
20 changes: 15 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
module github.com/stackloklabs/gollm

go 1.22.1
go 1.22.2

toolchain go1.22.8

require (
github.com/google/uuid v1.6.0
github.com/jackc/pgx/v4 v4.18.3
github.com/pgvector/pgvector-go v0.2.2
github.com/qdrant/go-client v1.12.0
github.com/stretchr/testify v1.9.0
)

require (
Expand All @@ -18,9 +22,15 @@ require (
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgtype v1.14.0 // indirect
github.com/jackc/puddle v1.3.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/stretchr/testify v1.9.0 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/text v0.16.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/net v0.29.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.18.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
google.golang.org/grpc v1.66.2 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
42 changes: 14 additions & 28 deletions pkg/db/pgvector.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
// It includes implementations for vector storage and retrieval using PostgreSQL
// with the pgvector extension, enabling efficient similarity search operations
// on high-dimensional vector data.

package db

import (
Expand All @@ -34,11 +35,9 @@ type PGVector struct {
conn *pgxpool.Pool
}

// Document represents a single document in the vector database.
// It contains a unique identifier and associated metadata.
type Document struct {
ID string
Metadata map[string]interface{}
// Close closes the PostgreSQL connection pool.
func (pg *PGVector) Close() {
pg.conn.Close()
}

// NewPGVector creates a new PGVector instance with a connection to the PostgreSQL database.
Expand Down Expand Up @@ -67,26 +66,25 @@ func NewPGVector(connString string) (*PGVector, error) {
//
// Returns:
// - An error if the saving operation fails, nil otherwise.
func (pg *PGVector) SaveEmbedding(ctx context.Context, docID string, embedding []float32, metadata map[string]interface{}) error {
// Create a pgvector.Vector type from the float32 slice
var query string
//
// SaveEmbeddings stores a document embedding and associated metadata in the PostgreSQL database, implementing the VectorDatabase interface.
func (pg *PGVector) SaveEmbeddings(ctx context.Context, docID string, embedding []float32, metadata map[string]interface{}) error {
vector := pgvector.NewVector(embedding)

Check failure on line 72 in pkg/db/pgvector.go

View workflow job for this annotation

GitHub Actions / Run golangci-lint

undefined: pgvector (typecheck)

// Determine the table based on the embedding length
var query string
switch len(embedding) {
case 1536:
query = `INSERT INTO openai_embeddings (doc_id, embedding, metadata) VALUES ($1, $2, $3)`

case 1024:
query = `INSERT INTO ollama_embeddings (doc_id, embedding, metadata) VALUES ($1, $2, $3)`
default:
return fmt.Errorf("unsupported embedding length: %d", len(embedding))
}
// Construct the query to insert the vector into the database

// Execute the query with the pgvector.Vector type
// Execute the query to insert the vector into the database
_, err := pg.conn.Exec(ctx, query, docID, vector, metadata)
if err != nil {
// Log the error for debugging purposes
return fmt.Errorf("failed to insert document: %w", err)
}
return nil
Expand Down Expand Up @@ -167,21 +165,9 @@ func ConvertEmbeddingToPGVector(embedding []float32) string {
return fmt.Sprintf("{%s}", strings.Join(strValues, ","))
}

// CombineQueryWithContext combines the query and retrieved documents' content to provide context for generation.
func CombineQueryWithContext(query string, docs []Document) string {
var contextStr string
for _, doc := range docs {
// Cast doc.Metadata["content"] to a string
if content, ok := doc.Metadata["content"].(string); ok {
contextStr += content + "\n"
}
}
return fmt.Sprintf("Context: %s\nQuery: %s", contextStr, query)
}

// InsertDocument insert a document into the vector store
func InsertDocument(ctx context.Context, vectorDB *PGVector, content string, embedding []float32) error {
// Generate a unique document ID (for simplicity, using a static value for testing)
// InsertDocument inserts a document into the PGVector store, implementing the VectorDatabase interface.
func (pg *PGVector) InsertDocument(ctx context.Context, content string, embedding []float32) error {
// Generate a unique document ID (for simplicity, using UUID)
docID := fmt.Sprintf("doc-%s", uuid.New().String())

// Create metadata
Expand All @@ -190,7 +176,7 @@ func InsertDocument(ctx context.Context, vectorDB *PGVector, content string, emb
}

// Save the document and its embedding into the vector store
err := vectorDB.SaveEmbedding(ctx, docID, embedding, metadata)
err := pg.SaveEmbeddings(ctx, docID, embedding, metadata)
if err != nil {
return fmt.Errorf("error saving embedding: %v", err)
}
Expand Down
Loading

0 comments on commit e72a48d

Please sign in to comment.