feat(llm): improve summary and grouping, and increase timeout #44

Merged
eleith merged 1 commits from gemini/feature/add-summary-stats into main 2025-08-19 03:31:41 +00:00
2 changed files with 39 additions and 23 deletions

No files matched your search

+13 -3
View File
@@ -23,7 +23,7 @@ const (
)
const (
LLMTimeout = 2 * time.Minute
LLMTimeout = 5 * time.Minute
DayGroupLayout = "2006-01-02"
DayGroupTitleLayout = "Jan 2, 2006"
)
@@ -171,7 +171,9 @@ type llmEntry struct {
FeedTitle string `json:"feed_title"`
}
const llmPrompt = `You are an expert news editor with a talent for identifying the most important and interesting information from a large volume of content. Your primary goal is to save the user time by providing a high-level, insightful overview of their news feeds.
const llmPrompt = `You are an expert content curator with a talent for identifying the most important and interesting information from a large volume of content. Your primary goal is to save the user time by providing a high-level, insightful overview of their content feeds.
The content can come from a variety of sources, including news sites, blogs, forums like Reddit, and social media like Bluesky. Your tone should be that of a helpful assistant, not a news editor.
Given the following list of entries, your task is to perform two distinct functions:
@@ -185,6 +187,7 @@ Given the following list of entries, your task is to perform two distinct functi
* The number of groups should be driven by the content itself. **Do not create a group for a single entry unless it represents a major, unique event.** The ideal number of groups is one that best helps a user skim the content. A group can contain many entries if they are all highly related.
* Group titles should be short, descriptive, and useful for skimming (e.g., "AI Industry News," "Project Updates," "Global Politics").
* Within each group, you must rank the 'entries' by importance, with the most significant or actionable item appearing first.
* The goal of grouping is to help with reading, so if a few entries are about some similar topic, those are worth grouping because the user can just read one and skim the rest.
Return the response as a JSON object according to the desired responseSchema.
@@ -304,5 +307,12 @@ func (g *LLMGrouper) GroupEntries(entries *miniflux.Entries) ([]*models.EntryGro
}
}
return entryGroups, response.Summary
feedIDs := make(map[int64]bool)
for _, entry := range *entries {
feedIDs[entry.FeedID] = true
}
statsSummary := fmt.Sprintf("You have %d entries from %d feeds.", len(*entries), len(feedIDs))
return entryGroups, fmt.Sprintf("%s\n\n%s", response.Summary, statsSummary)
}
+26 -20
View File
@@ -34,29 +34,33 @@ func (m *mockLLMService) GenerateContent(ctx context.Context, prompt string, sch
func createDayGrouperMockEntries() *miniflux.Entries {
return &miniflux.Entries{
{
ID: 1,
Title: "Entry 1 - Jan 2",
Date: time.Date(2024, time.January, 2, 10, 0, 0, 0, time.UTC),
Feed: &miniflux.Feed{ID: 100, Title: "Feed A"},
ID: 1,
Title: "Entry 1 - Jan 2",
Date: time.Date(2024, time.January, 2, 10, 0, 0, 0, time.UTC),
FeedID: 100,
Feed: &miniflux.Feed{ID: 100, Title: "Feed A"},
},
{
ID: 2,
Title: "Entry 2 - Jan 1",
Date: time.Date(2024, time.January, 1, 10, 0, 0, 0, time.UTC),
Feed: &miniflux.Feed{ID: 200, Title: "Feed B"},
ID: 2,
Title: "Entry 2 - Jan 1",
Date: time.Date(2024, time.January, 1, 10, 0, 0, 0, time.UTC),
FeedID: 200,
Feed: &miniflux.Feed{ID: 200, Title: "Feed B"},
},
{
ID: 3,
Title: "Entry 3 - Jan 2",
Date: time.Date(2024, time.January, 2, 11, 0, 0, 0, time.UTC),
Feed: &miniflux.Feed{ID: 100, Title: "Feed A"},
ID: 3,
Title: "Entry 3 - Jan 2",
Date: time.Date(2024, time.January, 2, 11, 0, 0, 0, time.UTC),
FeedID: 100,
Feed: &miniflux.Feed{ID: 100, Title: "Feed A"},
},
{
ID: 4,
Title: "Entry 4 - Jan 1",
ID: 4,
Title: "Entry 4 - Jan 1",
Content: "Content of entry 4 about Go concurrency.",
Date: time.Date(2024, time.January, 1, 12, 0, 0, 0, time.UTC),
Feed: &miniflux.Feed{ID: 200, Title: "Feed B"},
Date: time.Date(2024, time.January, 1, 12, 0, 0, 0, time.UTC),
FeedID: 200,
Feed: &miniflux.Feed{ID: 200, Title: "Feed B"},
},
}
}
@@ -199,8 +203,9 @@ func TestLLMGrouper_GroupEntries(t *testing.T) {
grouper := &LLMGrouper{LLMService: mockLLM}
groups, summary := grouper.GroupEntries(entries)
if summary != "This is a summary of all entries." {
t.Errorf("Expected summary \"This is a summary of all entries.\", got \"%s\"", summary)
expectedSummary := "This is a summary of all entries.\n\nYou have 4 entries from 2 feeds."
if summary != expectedSummary {
t.Errorf("Expected summary \"%s\", got \"%s\"", expectedSummary, summary)
}
if len(groups) != 2 {
@@ -287,8 +292,9 @@ func TestLLMGrouper_GroupEntries_WithDuplicateEntries(t *testing.T) {
grouper := &LLMGrouper{LLMService: mockLLM}
groups, summary := grouper.GroupEntries(entries)
if summary != "This is a summary of all entries." {
t.Errorf("Expected summary \"This is a summary of all entries.\", got \"%s\"", summary)
expectedSummary := "This is a summary of all entries.\n\nYou have 4 entries from 2 feeds."
if summary != expectedSummary {
t.Errorf("Expected summary \"%s\", got \"%s\"", expectedSummary, summary)
}
if len(groups) != 2 {