blob: 392e84f8759cd8402b80877d7edb2523deba0b90 [file]
package main
import (
"context"
"fmt"
"sync"
"cloud.google.com/go/pubsub"
"cloud.google.com/go/spanner"
"go.skia.org/infra/go/pubsub/sub"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/rag/go/config"
"go.skia.org/infra/rag/go/genai"
"go.skia.org/infra/rag/go/ingest/history"
"go.skia.org/infra/rag/go/ingest/sources"
"go.skia.org/infra/rag/go/topicstore"
)
// IngestionSubscriber provides a struct to manage ingestion from pubsub notifications.
type IngestionSubscriber struct {
subscription *pubsub.Subscription
historyIngestor *history.HistoryIngester
genAiClient genai.GenAIClient
evalSetPath string
queryEmbeddingModel string
dimensionality int32
defaultRepoName string
}
// NewIngestionSubscriber returns a new instance of the IngestionSubscriber.
func NewIngestionSubscriber(ctx context.Context, config config.ApiServerConfig, genAiClient genai.GenAIClient) (*IngestionSubscriber, error) {
// Generate the database identifier string and create the spanner client.
databaseName := fmt.Sprintf("projects/%s/instances/%s/databases/%s", config.SpannerConfig.ProjectID, config.SpannerConfig.InstanceID, config.SpannerConfig.DatabaseID)
spannerClient, err := spanner.NewClient(ctx, databaseName)
if err != nil {
sklog.Errorf("Error creating a spanner client")
return nil, err
}
topicStore := topicstore.NewRepositoryTopicStore(spannerClient)
sklog.Infof("Creating a new history ingester.")
ingester := history.New(topicStore, config.OutputDimensionality, config.DefaultRepoName)
sub, err := sub.NewWithSubName(ctx, config.IngestionConfig.Project, config.IngestionConfig.Topic, config.IngestionConfig.Subscription, 1)
if err != nil {
return nil, skerr.Wrap(err)
}
return &IngestionSubscriber{
subscription: sub,
historyIngestor: ingester,
genAiClient: genAiClient,
evalSetPath: config.IngestionConfig.EvalSetPath,
queryEmbeddingModel: config.QueryEmbeddingModel,
dimensionality: int32(config.OutputDimensionality),
defaultRepoName: config.DefaultRepoName,
}, nil
}
// Start creates a goroutine that listens for incoming pubsub messages to ingest.
func (subscriber *IngestionSubscriber) Start(ctx context.Context, wg *sync.WaitGroup) {
// Process all incoming PubSub requests.
go func() {
for {
// Wait for PubSub events.
err := subscriber.subscription.Receive(ctx, subscriber.processPubSubMessage)
if err != nil {
sklog.Errorf("Failed receiving pubsub message: %s", err)
wg.Done()
}
}
}()
}
// processPubSubMessage handles a single pubsub message.
func (s *IngestionSubscriber) processPubSubMessage(ctx context.Context, msg *pubsub.Message) {
sklog.Infof("Received pubsub message: %v", msg)
pubsubSource, err := sources.NewPubSubSource(ctx, msg, s.historyIngestor, s.genAiClient, s.evalSetPath, s.queryEmbeddingModel, s.dimensionality, s.defaultRepoName)
if err != nil {
sklog.Errorf("Error creating pubsub source: %v", err)
}
err = pubsubSource.Ingest(ctx)
if err != nil {
sklog.Errorf("Error processing file: %v", err)
msg.Nack()
} else {
msg.Ack()
sklog.Infof("Ack'd message")
}
}