Rip out Ollama: direct_route only, no LLM layer
Pete moves to a remote host without Ollama access. Every source must declare a direct_route channel; the classifier, explainer, semantic dedup, !explain summaries, feed_hint, and the recent_headlines / classification_log tables are gone. Deterministic dedup (canonical URL, headline_norm, per-channel cooldown) remains.
This commit is contained in:
84
main.go
84
main.go
@@ -10,9 +10,7 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"pete/internal/classifier"
|
||||
"pete/internal/config"
|
||||
"pete/internal/explainer"
|
||||
"pete/internal/ingestion"
|
||||
"pete/internal/matrix"
|
||||
"pete/internal/poster"
|
||||
@@ -43,7 +41,6 @@ func main() {
|
||||
slog.Info("config loaded",
|
||||
"sources", len(cfg.Sources),
|
||||
"channels", len(cfg.Matrix.Channels),
|
||||
"ollama_model", cfg.Ollama.Model,
|
||||
)
|
||||
|
||||
// Initialize database
|
||||
@@ -72,17 +69,11 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Create Ollama client and classifier
|
||||
ollamaClient := classifier.NewOllamaClient(cfg.Ollama)
|
||||
cls := classifier.NewClassifier(ollamaClient, mx)
|
||||
|
||||
// Create post queue
|
||||
queue := poster.NewQueue(cfg.Posting, mx)
|
||||
|
||||
// Wire reaction handler + ❓ → summary hook
|
||||
// Wire reaction handler
|
||||
mx.SetReactionHandler(poster.HandleReaction)
|
||||
exp := explainer.New(mx, ollamaClient)
|
||||
poster.SetReactionCallback(exp.Handle)
|
||||
|
||||
// Wire !post command: force-publish next queued story for the room's channel.
|
||||
mx.SetMessageHandler(func(channel string, roomID id.RoomID, eventID id.EventID, sender id.UserID, body string) {
|
||||
@@ -142,70 +133,37 @@ func main() {
|
||||
|
||||
roundRobinMode := cfg.Posting.RoundRobin.Enabled
|
||||
|
||||
// Build the pipeline callback: classify → enqueue (or just classify+store
|
||||
// when round-robin mode is enabled; the scheduler does the enqueueing).
|
||||
processItem := func(ctx context.Context, item *ingestion.FeedItem) {
|
||||
result, err := cls.Classify(ctx, item)
|
||||
if err != nil {
|
||||
slog.Error("classification failed, will retry next cycle",
|
||||
"guid", item.GUID,
|
||||
"err", err,
|
||||
)
|
||||
return // story stays unclassified for retry
|
||||
}
|
||||
|
||||
// Add to recent headlines for future dedup
|
||||
storage.InsertRecentHeadline(item.GUID, item.Headline, item.Source)
|
||||
|
||||
if !result.ShouldPost {
|
||||
// Mark classified with sentinel channel so it's not retried
|
||||
// Pipeline callback: route the story to its direct_route channel, then
|
||||
// either enqueue immediately or leave it for the round-robin scheduler.
|
||||
processItem := func(_ context.Context, item *ingestion.FeedItem) {
|
||||
if item.DirectRoute == "" {
|
||||
slog.Error("item has no direct_route, skipping", "guid", item.GUID, "source", item.Source)
|
||||
storage.MarkClassified(item.GUID, "_discarded", "[]")
|
||||
slog.Info("story gated out",
|
||||
"guid", item.GUID,
|
||||
"rationale", result.Rationale,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Mark classified with actual channel
|
||||
platforms := storage.MarshalPlatforms(result.Platforms)
|
||||
storage.MarkClassified(item.GUID, result.Channel, platforms)
|
||||
|
||||
if result.DuplicateOf != nil {
|
||||
// Overwrite with sentinel so the round-robin scheduler doesn't pick
|
||||
// this up later. Immediate-mode posting already returned above.
|
||||
storage.MarkClassified(item.GUID, "_duplicate", "[]")
|
||||
slog.Info("story deduplicated",
|
||||
"guid", item.GUID,
|
||||
"duplicate_of", *result.DuplicateOf,
|
||||
)
|
||||
return
|
||||
}
|
||||
channel := item.DirectRoute
|
||||
storage.MarkClassified(item.GUID, channel, "[]")
|
||||
|
||||
if roundRobinMode {
|
||||
// Story stays in DB classified+postable; the scheduler will pick
|
||||
// it up on the next rotation tick.
|
||||
slog.Info("story classified, awaiting round-robin tick",
|
||||
"guid", item.GUID, "source", item.Source, "channel", result.Channel)
|
||||
slog.Info("story routed, awaiting round-robin tick",
|
||||
"guid", item.GUID, "source", item.Source, "channel", channel)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate image
|
||||
imageURL := ""
|
||||
if item.ImageURL != "" && ingestion.ValidateImageURL(item.ImageURL) {
|
||||
imageURL = item.ImageURL
|
||||
}
|
||||
|
||||
// Enqueue for posting
|
||||
queue.Enqueue(poster.QueueItem{
|
||||
GUID: item.GUID,
|
||||
Headline: item.Headline,
|
||||
Lede: item.Lede,
|
||||
ImageURL: imageURL,
|
||||
ArticleURL: item.ArticleURL,
|
||||
Source: item.Source,
|
||||
Channel: result.Channel,
|
||||
Platforms: result.Platforms,
|
||||
Source: item.Source,
|
||||
Channel: channel,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -238,7 +196,7 @@ func main() {
|
||||
}
|
||||
|
||||
// Run maintenance on startup
|
||||
storage.RunMaintenance(cfg.Storage.RecentWindowHours, cfg.Storage.ClassificationLogDays)
|
||||
storage.RunMaintenance()
|
||||
|
||||
// Wait for shutdown signal
|
||||
sig := <-sigCh
|
||||
@@ -272,15 +230,13 @@ func runSeed(cfg *config.Config) {
|
||||
Lede: item.Lede,
|
||||
ImageURL: item.ImageURL,
|
||||
ArticleURL: item.ArticleURL,
|
||||
Source: src.Name,
|
||||
FeedHint: src.FeedHint,
|
||||
Source: src.Name,
|
||||
Classified: true,
|
||||
SeenAt: timeNow(),
|
||||
}); err != nil {
|
||||
slog.Error("seed: insert failed", "guid", item.GUID, "err", err)
|
||||
continue
|
||||
}
|
||||
storage.InsertRecentHeadline(item.GUID, item.Headline, src.Name)
|
||||
total++
|
||||
}
|
||||
slog.Info("seed: source done", "source", src.Name, "items", len(items))
|
||||
@@ -307,8 +263,6 @@ func runTest(cfg *config.Config, sourceName string) {
|
||||
if len(items) > 0 {
|
||||
item := items[0]
|
||||
item.Source = s.Name
|
||||
item.FeedHint = s.FeedHint
|
||||
item.Tier = s.Tier
|
||||
item.DirectRoute = s.DirectRoute
|
||||
testItem = &item
|
||||
src = s
|
||||
@@ -326,10 +280,10 @@ func runTest(cfg *config.Config, sourceName string) {
|
||||
"guid", testItem.GUID,
|
||||
)
|
||||
|
||||
// Determine channel
|
||||
channel := "politics" // default
|
||||
if testItem.DirectRoute != nil {
|
||||
channel = *testItem.DirectRoute
|
||||
channel := testItem.DirectRoute
|
||||
if channel == "" {
|
||||
slog.Error("test: source has no direct_route", "source", src.Name)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Validate image
|
||||
@@ -351,7 +305,7 @@ func runTest(cfg *config.Config, sourceName string) {
|
||||
Headline: testItem.Headline,
|
||||
ArticleURL: testItem.ArticleURL,
|
||||
Lede: testItem.Lede,
|
||||
Source: testItem.Source,
|
||||
Source: testItem.Source,
|
||||
Channel: channel,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user