all repos — llm_aggregator @ 7b6943f1a3f252697b0e16d94c9635a5e064090b

A CLI tool to aggregate RSS feeds and summarise them with LLMs

internal/runtime/runtime.go (view raw)

  1package runtime
  2
  3import (
  4	"context"
  5	"fmt"
  6	"io"
  7	"os"
  8	"time"
  9
 10	"llm_aggregator/internal/aggregator"
 11	"llm_aggregator/internal/llm"
 12	"llm_aggregator/internal/output"
 13	"llm_aggregator/internal/processor"
 14)
 15
 16// Runtime holds the execution context for the aggregator
 17type Runtime struct {
 18	// Configuration
 19	FeedsFile          string
 20	MaxArticlesPerFeed int
 21	MaxDaysOld         int
 22	MaxTotalArticles   int
 23	IncludeKeywords    []string
 24	ExcludeKeywords    []string
 25	APIKey             string
 26	Model              string
 27	MaxTokens          int
 28	Temperature        float64
 29	Prompt             string
 30	SystemPrompt       string
 31	Output             string
 32	OutputFile         string
 33	IncludeArticles    bool
 34
 35	// State
 36	Articles []map[string]any
 37	Summary  string
 38	Error    error
 39}
 40
 41// NewRuntime creates a new runtime with default values
 42func NewRuntime() *Runtime {
 43	return &Runtime{
 44		MaxArticlesPerFeed: 10,
 45		MaxDaysOld:         7,
 46		MaxTotalArticles:   50,
 47		Model:              "deepseek-chat",
 48		MaxTokens:          2000,
 49		Temperature:        0.7,
 50		Output:             "text",
 51		SystemPrompt:       "You are a helpful assistant that summarises news articles.",
 52	}
 53}
 54
 55// Execute runs the full aggregation pipeline
 56func (r *Runtime) Execute(ctx context.Context) error {
 57	// Step 1: Aggregate feeds
 58	feedAgg := aggregator.NewFeedAggregator(
 59		r.MaxArticlesPerFeed,
 60		r.MaxDaysOld,
 61		5000, // max content length
 62	)
 63
 64	articles, err := feedAgg.ParseFeedsFromFile(r.FeedsFile)
 65	if err != nil {
 66		return fmt.Errorf("error aggregating feeds: %w", err)
 67	}
 68
 69	if len(articles) == 0 {
 70		return fmt.Errorf("no articles found")
 71	}
 72
 73	// Step 2: Process content
 74	contentProc := processor.NewContentProcessor(
 75		r.MaxTotalArticles,
 76		3000, // max content per article
 77		r.IncludeKeywords,
 78		r.ExcludeKeywords,
 79	)
 80
 81	processedArticles := contentProc.ProcessArticles(articles, "date", true)
 82
 83	if len(processedArticles) == 0 {
 84		return fmt.Errorf("no articles passed filtering")
 85	}
 86
 87	r.Articles = processedArticles
 88
 89	// Step 3: Initialise Deepseek client
 90	deepseek, err := llm.NewDeepseekClient(
 91		r.APIKey,
 92		"", // default base URL
 93		r.Model,
 94		r.MaxTokens,
 95		r.Temperature,
 96	)
 97	if err != nil {
 98		return fmt.Errorf("error initialising Deepseek client: %w", err)
 99	}
100
101	// Step 4: Get summary from Deepseek
102	summary, err := deepseek.SummariseArticles(
103		processedArticles,
104		r.Prompt,
105		r.SystemPrompt,
106	)
107	if err != nil {
108		return fmt.Errorf("error getting summary from Deepseek: %w", err)
109	}
110
111	r.Summary = summary
112	return nil
113}
114
115// WriteOutput writes the formatted output to the specified writer
116func (r *Runtime) WriteOutput(writer io.Writer) error {
117	formatter, err := output.NewFormatter(r.Output)
118	if err != nil {
119		return fmt.Errorf("error creating formatter: %w", err)
120	}
121
122	outputData := map[string]any{
123		"title":          fmt.Sprintf("LLM Aggregator Summary - %s", time.Now().Format("2006-01-02 15:04")),
124		"prompt":         r.Prompt,
125		"model":          r.Model,
126		"articles_count": len(r.Articles),
127		"summary":        r.Summary,
128		"timestamp":      time.Now().Format(time.RFC3339),
129	}
130
131	if r.IncludeArticles {
132		outputData["articles"] = r.Articles
133	}
134
135	formattedOutput, err := formatter.FormatData(outputData)
136	if err != nil {
137		return fmt.Errorf("error formatting output: %w", err)
138	}
139
140	if _, err := fmt.Fprint(writer, formattedOutput); err != nil {
141		return fmt.Errorf("error writing output: %w", err)
142	}
143
144	return nil
145}
146
147// WriteOutputToFile writes the output to the specified file
148func (r *Runtime) WriteOutputToFile() error {
149	if r.OutputFile == "" {
150		return r.WriteOutput(os.Stdout)
151	}
152
153	file, err := os.Create(r.OutputFile)
154	if err != nil {
155		return fmt.Errorf("error creating output file: %w", err)
156	}
157	defer file.Close()
158
159	return r.WriteOutput(file)
160}
161