Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Analyst Feature #43

Merged
merged 1 commit into from
Jan 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,27 @@ openai_model:
summary_feeds:
```

### Analyst LLM Feature
The Analyst feature enables you to gather articles from specified feeds and analyze them using a prompt sent to a language model like GPT-4o (default). The result is included in the daily digest under an Analysis section. You write on your analyst_prompt setting what do you want the analyst to do on your behalf, for example picking relevant news to your liking (example: a cybersecurity expert interested only in certain type of attack), or having an investing analyst suggesting investment opportunities, etc.

Configuration Example of an analyst finding investment opportunities:

```yaml
openai_api_key: sk-xxxxxxxxxxxxxxxxx
analyst_feeds:
- https://feeds.bbci.co.uk/news/business/rss.xml
analyst_prompt: You are a world-class investing expert. Analyze the provided list of articles for potential investment opportunities. If no direct opportunities are found, identify industries, regions, or trends that could have indirect impacts on the investment landscape.
```

How it Works:
Gathering Articles: The RSS feeds specified in analyst_feeds are fetched, and the titles along with their rss descriptions are attached to the analyst_prompt to form a single input prompt.
Then the prompt is sent to the specified language model (analyst_model), and the response is included in the daily markdown file under the Analysis section.

Default model is OpenAI's gpt-4o but to override model add configuration:
```
analyst_model: o1-preview
```

### Summarization of Articles using ChatGPT

In order to use the summarization feature, you'll first need to set up an OpenAI account. If you haven't already done so, you can sign up [here](https://platform.openai.com/login?launch). Once registered, you'll need to acquire an OpenAI API key which can be found [here](https://platform.openai.com/account/api-keys).
Expand Down
78 changes: 78 additions & 0 deletions analyst.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package main

import (
"context"
"fmt"
"strings"

"github.com/mmcdole/gofeed"
openai "github.com/sashabaranov/go-openai"
"github.com/spf13/viper"
)

const defaultLimit = 20 // default number of articles per feed for analysis
var model = openai.GPT4o

func generateAnalysis(fp *gofeed.Parser, writer Writer) {
if !viper.IsSet("analyst_feeds") || !viper.IsSet("analyst_prompt") {
return
}

analystFeeds := viper.GetStringSlice("analyst_feeds")
analystPrompt := viper.GetString("analyst_prompt")
analystModel := viper.GetString("analyst_model")

var articleTitles []string
for _, feedURL := range analystFeeds {
parsedFeed := parseFeed(fp, feedURL, defaultLimit)
if parsedFeed == nil {
continue
}
for _, item := range parsedFeed.Items {
articleTitles = append(articleTitles, item.Title+": "+item.Description) // add also description for better context
}
}

if len(articleTitles) == 0 {
return
}

prompt := fmt.Sprintf("%s\n\n%s", analystPrompt, strings.Join(articleTitles, "\n"))
analysis := getLLMAnalysis(prompt, analystModel)

if analysis != "" {
writer.write("\n## Daily Analysis:\n")
writer.write(analysis + "\n")
}
}

func getLLMAnalysis(prompt string, analystModel string) string {
clientConfig := openai.DefaultConfig(openaiApiKey)
if openaiBaseURL != "" {
clientConfig.BaseURL = openaiBaseURL
}
if analystModel != "" {
model = analystModel
}
client := openai.NewClientWithConfig(clientConfig)

resp, err := client.CreateChatCompletion(
context.Background(),
openai.ChatCompletionRequest{
Model: model,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: prompt,
},
},
},
)

if err != nil {
fmt.Printf("ChatCompletion error: %v\n", err)
return ""
}

return resp.Choices[0].Message.Content
}
7 changes: 6 additions & 1 deletion config.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,12 @@ openai_api_key:
openai_base_url:
openai_model:
summary_feeds:
show_images: false`
show_images: false
analyst_feeds:
- https://feeds.bbci.co.uk/news/business/rss.xml
analyst_prompt:
analyst_model:
`

func parseOPML(xmlContent []byte) []RSS {
o := Opml{}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.18
require (
github.com/mmcdole/gofeed v1.1.3
github.com/nathan-osman/go-sunrise v1.1.0
github.com/sashabaranov/go-openai v1.14.2
github.com/sashabaranov/go-openai v1.36.1
github.com/spf13/viper v1.16.0
modernc.org/sqlite v1.20.0
)
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,8 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sashabaranov/go-openai v1.14.2 h1:5DPTtR9JBjKPJS008/A409I5ntFhUPPGCmaAihcPRyo=
github.com/sashabaranov/go-openai v1.14.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/sashabaranov/go-openai v1.36.1 h1:EVfRXwIlW2rUzpx6vR+aeIKCK/xylSrVYAx1TMTSX3g=
github.com/sashabaranov/go-openai v1.36.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/savioxavier/termlink v1.2.1 h1:O9ZQvk9BPQQK4JQeMB56ZfV8uam0Ts+f97mJme7+dq8=
github.com/savioxavier/termlink v1.2.1/go.mod h1:WA7FTALNwN41NGnmQMIrnjAYTsEhIAZ4RuzgEiB0Jp8=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ func main() {
writer := getWriter()
displayWeather(writer)
displaySunriseSunset(writer)
generateAnalysis(fp, writer)

for _, feed := range myFeeds {
parsedFeed := parseFeed(fp, feed.url, feed.limit)
Expand All @@ -26,6 +27,5 @@ func main() {
}
}

// Close the database connection after processing all the feeds
defer db.Close()
}
Loading