refactor: generalise Deepseek-specific naming to generic LLM
- Rename `DeepseekClient` to `LLMClient` in `internal/llm/deepseek.go`
- Update constructor `NewDeepseekClient` → `NewLLMClient`
- Change all comments and log messages from "Deepseek" to "LLM"
- Update `internal/runtime/runtime.go` to use `NewLLMClient` instead
- Replace all references to "Deepseek API" with "LLM API" across:
- `README.md` – section headings, environment variables, examples
- `configs/config.example.toml` – comments and section titles
- `internal/cli/args.go` – flag help text, description, environment
note
- `internal/config/config.go` – struct comments and bindEnv calls
- Keep default model as `deepseek-chat` and base URL as
`https://api.deepseek.com`
- The client remains compatible with Deepseek but clearly supports any
OpenAI‑compatible LLM provider
- Improve error messages to be provider‑agnostic (e.g., "invalid API
key" without mentioning Deepseek)
@@ -53,10 +53,10 @@ --include-keywords LIST Comma-separated list of keywords to include (case‑insensitive)
--exclude-keywords LIST Comma-separated list of keywords to exclude (case‑insensitive) --include-articles Include original articles in JSON output -### Deepseek Configuration +### LLM Configuration --temperature VALUE Sampling temperature (0.0 to 1.0) (default: 0.7) - --system-prompt TEXT Custom system prompt for Deepseek + --system-prompt TEXT Custom system prompt for LLM ### Examples@@ -136,13 +136,13 @@ # Content filtering (comma-separated keywords)
# include_keywords = "linux,opensource" # exclude_keywords = "windows,microsoft" -# Deepseek API options +# LLM API options # api_key = "your_api_key_here" # Can also be set via LLM_AGGREGATOR_API_KEY env var model = "deepseek-chat" max_tokens = 4000 temperature = 0.7 -# System prompt for Deepseek API +# System prompt for LLM API system_prompt = """You are an expert analyst and summariser. You analyse content from multiple sources and provide concise, insightful summaries based on user requests.@@ -158,7 +158,7 @@ ### Environment variables
All configuration options can also be set via environment variables with the `LLM_AGGREGATOR_` prefix: -- `LLM_AGGREGATOR_API_KEY` – Deepseek API key +- `LLM_AGGREGATOR_API_KEY` – LLM API key - `LLM_AGGREGATOR_MODEL` – Model name (default: "deepseek-chat") - `LLM_AGGREGATOR_MAX_TOKENS` – Maximum tokens in response (default: 4000) - `LLM_AGGREGATOR_TEMPERATURE` – Sampling temperature (default: 0.7)
@@ -93,7 +93,7 @@ } else if cfg.ExcludeKeywords != "" {
rt.ExcludeKeywords = cli.ParseKeywords(cfg.ExcludeKeywords) } - // Deepseek API options - CLI args override config + // LLM API options - CLI args override config if args.APIKey != "" { rt.APIKey = args.APIKey } else if cfg.APIKey != "" {
@@ -10,13 +10,13 @@ # Content filtering (comma-separated keywords)
# include_keywords = "linux,opensource" # exclude_keywords = "windows,microsoft" -# Deepseek API options +# LLM API options # api_key = "your_api_key_here" # Can also be set via LLM_AGGREGATOR_API_KEY env var model = "deepseek-chat" max_tokens = 4000 temperature = 0.7 -# System prompt for Deepseek API +# System prompt for LLM API system_prompt = """You are an expert analyst and summariser. You analyse content from multiple sources and provide concise, insightful summaries based on user requests.
@@ -27,9 +27,9 @@ // Content filtering
IncludeKeywords string `arg:"--include-keywords" help:"Comma-separated list of keywords to include (case-insensitive)"` ExcludeKeywords string `arg:"--exclude-keywords" help:"Comma-separated list of keywords to exclude (case-insensitive)"` - // Deepseek API options + // LLM API options APIKey string `arg:"--api-key" help:"OpenAI-compatible API key (default: read from LLM_AGGREGATOR_API_KEY env var)"` - Model string `arg:"--model" help:"Deepseek model to use" default:"deepseek-chat"` + Model string `arg:"--model" help:"LLM model to use" default:"deepseek-chat"` MaxTokens int `arg:"--max-tokens" help:"Maximum tokens in response" default:"4000"` Temperature float64 `arg:"--temperature" help:"Sampling temperature (0.0 to 1.0)" default:"0.7"`@@ -39,7 +39,7 @@ OutputFile string `arg:"--output-file" help:"Write output to file (default: stdout)"`
IncludeArticles bool `arg:"--include-articles" help:"Include original articles in JSON output"` // System options - SystemPrompt string `arg:"--system-prompt" help:"Custom system prompt for Deepseek"` + SystemPrompt string `arg:"--system-prompt" help:"Custom system prompt for LLM"` TUI bool `arg:"--tui" help:"Enable TUI interface with progress bar"` Verbose bool `arg:"-v,--verbose" help:"Show verbose output"` ShowVersion bool `arg:"--version" help:"Show version"`@@ -52,13 +52,13 @@ }
// Description returns the program description. func (Args) Description() string { - return `LLM Aggregator - Aggregate RSS feeds and summarise with Deepseek API + return `LLM Aggregator - Aggregate RSS feeds and summarise with LLM API Examples: # Basic usage with prompts llm_aggregator --feeds-file feeds.txt --prompt "What are the latest trends in free software?" - # With custom Deepseek model + # With custom LLM model llm_aggregator --feeds-file feeds.txt --prompt "Summarise tech news" --model deepseek-coder # Output to JSON file@@ -68,7 +68,7 @@ # Filter by keywords
llm_aggregator --feeds-file feeds.txt --prompt "Linux news" --include-keywords linux,opensource Environment Variables: - LLM_AGGREGATOR_API_KEY: Your Deepseek API key (required if not provided via --api-key)` + LLM_AGGREGATOR_API_KEY: Your LLM API key (required if not provided via --api-key)` } // ParseKeywords parses comma-separated keywords string into list.
@@ -17,7 +17,7 @@ MaxTotalArticles int `mapstructure:"max_total_articles"`
IncludeKeywords string `mapstructure:"include_keywords"` ExcludeKeywords string `mapstructure:"exclude_keywords"` - // Deepseek API options + // LLM API options APIKey string `mapstructure:"api_key"` Model string `mapstructure:"model"` MaxTokens int `mapstructure:"max_tokens"`@@ -107,7 +107,7 @@ v.BindEnv("max_total_articles", "LLM_AGGREGATOR_MAX_TOTAL_ARTICLES")
v.BindEnv("include_keywords", "LLM_AGGREGATOR_INCLUDE_KEYWORDS") v.BindEnv("exclude_keywords", "LLM_AGGREGATOR_EXCLUDE_KEYWORDS") - // Deepseek API options + // LLM API options v.BindEnv("api_key", "LLM_AGGREGATOR_API_KEY") v.BindEnv("model", "LLM_AGGREGATOR_MODEL") v.BindEnv("max_tokens", "LLM_AGGREGATOR_MAX_TOKENS")
@@ -13,8 +13,8 @@ "github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/option" ) -// DeepseekClient is a client for interacting with Deepseek API. -type DeepseekClient struct { +// LLMClient is a client for interacting with LLM API. +type LLMClient struct { client openai.Client model string maxTokens int@@ -22,20 +22,20 @@ temperature float64
logger *progress.Context } -// NewDeepseekClient creates a new Deepseek client. -// apiKey: Deepseek API key (or read from LLM_AGGREGATOR_API_KEY env var) +// NewLLMClient creates a new LLM client. +// apiKey: LLM API key (or read from LLM_AGGREGATOR_API_KEY env var) // baseURL: API base URL (defaults to "https://api.deepseek.com") // model: Model to use (defaults to "deepseek-chat") // maxTokens: Maximum tokens in response (defaults to 4000) // temperature: Sampling temperature (0.0 to 1.0, defaults to 0.7) -func NewDeepseekClient(apiKey, baseURL, model string, maxTokens int, temperature float64) (*DeepseekClient, error) { +func NewLLMClient(apiKey, baseURL, model string, maxTokens int, temperature float64) (*LLMClient, error) { // Get API key from parameter or environment variable if apiKey == "" { apiKey = os.Getenv("LLM_AGGREGATOR_API_KEY") } if apiKey == "" || strings.TrimSpace(apiKey) == "" { return nil, fmt.Errorf( - "Deepseek API key is required. " + + "LLM API key is required. " + "Set LLM_AGGREGATOR_API_KEY environment variable or pass apiKey parameter", ) }@@ -54,13 +54,13 @@ if temperature == 0 {
temperature = 0.7 } - // Create OpenAI client configured for Deepseek + // Create OpenAI client configured for LLM client := openai.NewClient( option.WithAPIKey(apiKey), option.WithBaseURL(baseURL), ) - return &DeepseekClient{ + return &LLMClient{ client: client, model: model, maxTokens: maxTokens,@@ -68,8 +68,8 @@ temperature: temperature,
}, nil } -// SetLogger sets the logger for the Deepseek client -func (dc *DeepseekClient) SetLogger(logger *progress.Context) { +// SetLogger sets the logger for the LLM client +func (dc *LLMClient) SetLogger(logger *progress.Context) { dc.logger = logger }@@ -77,7 +77,7 @@ // SummariseArticles summarises a list of articles based on user prompt.
// articles: List of article maps // userPrompt: User's query/summarisation request // systemPrompt: Optional system prompt (defaults to helpful assistant) -func (dc *DeepseekClient) SummariseArticles( +func (dc *LLMClient) SummariseArticles( articles []map[string]any, userPrompt string, systemPrompt string,@@ -96,7 +96,7 @@ // Call API with messages
return dc.callAPIWithMessages(messages) } -func (dc *DeepseekClient) prepareContext(articles []map[string]any) string { +func (dc *LLMClient) prepareContext(articles []map[string]any) string { contextParts := []string{} for i, article := range articles {@@ -143,7 +143,7 @@
return strings.Join(contextParts, "\n") } -func (dc *DeepseekClient) createMessages(context, userPrompt, systemPrompt string) []openai.ChatCompletionMessageParamUnion { +func (dc *LLMClient) createMessages(context, userPrompt, systemPrompt string) []openai.ChatCompletionMessageParamUnion { if systemPrompt == "" { systemPrompt = `You are an expert analyst and summariser. You analyse content from multiple sources and provide@@ -171,11 +171,11 @@
return messages } -func (dc *DeepseekClient) callAPIWithMessages(messages []openai.ChatCompletionMessageParamUnion) (string, error) { +func (dc *LLMClient) callAPIWithMessages(messages []openai.ChatCompletionMessageParamUnion) (string, error) { ctx := context.Background() if dc.logger != nil { - dc.logger.Logf("Calling Deepseek API with model: %s", dc.model) + dc.logger.Logf("Calling LLM API with model: %s", dc.model) } response, err := dc.client.Chat.Completions.New(ctx, openai.ChatCompletionNewParams{@@ -189,15 +189,15 @@ if err != nil {
// Provide more specific error messages errStr := err.Error() if strings.Contains(errStr, "401") { - return "", fmt.Errorf("invalid API key. Please check your Deepseek API key") + return "", fmt.Errorf("invalid API key. Please check your LLM API key") } else if strings.Contains(errStr, "429") { return "", fmt.Errorf("rate limit exceeded. Please try again later") } else if strings.Contains(errStr, "500") { - return "", fmt.Errorf("Deepseek API server error. Please try again later") + return "", fmt.Errorf("LLM API server error. Please try again later") } else if strings.Contains(errStr, "404") { return "", fmt.Errorf("API endpoint not found. Please check the base URL and endpoint. OpenAI API uses /chat/completions") } - return "", fmt.Errorf("failed to connect to Deepseek API: %w", err) + return "", fmt.Errorf("failed to connect to LLM API: %w", err) } // Extract text from response@@ -210,7 +210,7 @@
// Print token usage if dc.logger != nil { dc.logger.Logf( - "Deepseek API response: %d prompt tokens, %d completion tokens", + "LLM API response: %d prompt tokens, %d completion tokens", response.Usage.PromptTokens, response.Usage.CompletionTokens, )@@ -220,7 +220,7 @@ return outputText, nil
} // AnalyseWithCustomPrompt analyses articles with custom system and user prompts. -func (dc *DeepseekClient) AnalyseWithCustomPrompt( +func (dc *LLMClient) AnalyseWithCustomPrompt( articles []map[string]any, systemPrompt string, userPrompt string,
@@ -104,11 +104,11 @@ r.Progress.SetArticleCount(len(articles), len(articles)-len(processedArticles))
r.Articles = processedArticles - // Step 3: Initialise Deepseek client + // Step 3: Initialise LLM client r.Progress.SetStage("Connecting to LLM") r.Progress.SetSubStage(fmt.Sprintf("Using model: %s", r.Model)) - deepseek, err := llm.NewDeepseekClient( + deepseek, err := llm.NewLLMClient( r.APIKey, "", // default base URL r.Model,@@ -116,11 +116,11 @@ r.MaxTokens,
r.Temperature, ) if err != nil { - return fmt.Errorf("error initialising Deepseek client: %w", err) + return fmt.Errorf("error initialising LLM client: %w", err) } deepseek.SetLogger(progCtx) // MODIFIED: Pass the new progress context - // Step 4: Get summary from Deepseek + // Step 4: Get summary from LLM r.Progress.SetStage("Getting summary") r.Progress.SetSubStage(fmt.Sprintf("Sending %d articles to LLM", len(processedArticles)))@@ -130,7 +130,7 @@ r.Prompt,
r.SystemPrompt, ) if err != nil { - return fmt.Errorf("error getting summary from Deepseek: %w", err) + return fmt.Errorf("error getting summary from LLM: %w", err) } r.Summary = summary