From feb06e51ffc9fe9b59889b638e01f7b818209b87 Mon Sep 17 00:00:00 2001 From: Olivier Tremblay Date: Sun, 16 Nov 2025 14:13:20 -0500 Subject: [PATCH] refactor: extract callSummarizationEndpoint into Summarizer interface for multiple implementations Co-authored-by: aider (openai/qwen3-coder:30b-a3b-q4_K_M) --- cmd/acb/summarize.go | 72 ++++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/cmd/acb/summarize.go b/cmd/acb/summarize.go index c5cf9aa..78ee2b4 100644 --- a/cmd/acb/summarize.go +++ b/cmd/acb/summarize.go @@ -19,38 +19,16 @@ const defaultPrompt = `I will provide you, for a given period, with an employee I'd like you to summarize the employee's accomplishments for the quarter I'd like the summary for the accomplishments to be in prose form, in a few paragraphs separated based on areas of work. Keep answers to 500 words for the summary.` -// buildPrompt constructs the prompt string from PRs, issues, and tasks -func buildPrompt(employeename string, prs map[string][]contributions.PullRequest, issues []issues.Issue, tasks []vikunja.Task, prompt string) string { - // Build a prompt string - fullPrompt := prompt + fmt.Sprintf("\n\nHere's the PRs and Tickets for the employee %s:\n\n", employeename) - for repo, prList := range prs { - fullPrompt += fmt.Sprintf("Repository: %s\n", repo) - for _, pr := range prList { - fullPrompt += fmt.Sprintf("- Title: %s\n", pr.Title) - fullPrompt += fmt.Sprintf(" Body: %s\n", pr.Body) - } - } - fullPrompt += "Issues:\n" - for _, issue := range issues { - fullPrompt += fmt.Sprintf("Summary: %s\n", issue.Summary) - fullPrompt += fmt.Sprintf("Description: %s\n", issue.Description) - fullPrompt += "--------\n" - } - - // Save prompt to file for debugging - promptf, err := os.Create(fmt.Sprintf("prompt-%s-%d.json", employeename, time.Now().Unix())) - if err != nil { - fmt.Println(fmt.Errorf("error creating PR file: %w", err)) - os.Exit(1) - } - promptf.WriteString(fullPrompt) - defer promptf.Close() - - return fullPrompt +// Summarizer interface defines the contract for summarization implementations +type Summarizer interface { + Summarize(prompt string, endpoint string, token string, model string) (string, error) } -// callSummarizationEndpoint sends the prompt to an OpenAI-compatible endpoint for summarization -func callSummarizationEndpoint(fullPrompt string, openaiEndpoint string, openaiToken string, openaiModel string) (string, error) { +// OpenAISummarizer implements the Summarizer interface for OpenAI-compatible endpoints +type OpenAISummarizer struct{} + +// Summarize sends the prompt to an OpenAI-compatible endpoint for summarization +func (o *OpenAISummarizer) Summarize(fullPrompt string, openaiEndpoint string, openaiToken string, openaiModel string) (string, error) { // Create a JSON payload for the OpenAI API payload := struct { Model string `json:"model"` @@ -95,14 +73,44 @@ func callSummarizationEndpoint(fullPrompt string, openaiEndpoint string, openaiT return string(body), nil } +// buildPrompt constructs the prompt string from PRs, issues, and tasks +func buildPrompt(employeename string, prs map[string][]contributions.PullRequest, issues []issues.Issue, tasks []vikunja.Task, prompt string) string { + // Build a prompt string + fullPrompt := prompt + fmt.Sprintf("\n\nHere's the PRs and Tickets for the employee %s:\n\n", employeename) + for repo, prList := range prs { + fullPrompt += fmt.Sprintf("Repository: %s\n", repo) + for _, pr := range prList { + fullPrompt += fmt.Sprintf("- Title: %s\n", pr.Title) + fullPrompt += fmt.Sprintf(" Body: %s\n", pr.Body) + } + } + fullPrompt += "Issues:\n" + for _, issue := range issues { + fullPrompt += fmt.Sprintf("Summary: %s\n", issue.Summary) + fullPrompt += fmt.Sprintf("Description: %s\n", issue.Description) + fullPrompt += "--------\n" + } + + // Save prompt to file for debugging + promptf, err := os.Create(fmt.Sprintf("prompt-%s-%d.json", employeename, time.Now().Unix())) + if err != nil { + fmt.Println(fmt.Errorf("error creating PR file: %w", err)) + os.Exit(1) + } + promptf.WriteString(fullPrompt) + defer promptf.Close() + + return fullPrompt +} + // SummarizeData builds the prompt and calls the summarization endpoint -func SummarizeData(employeename string, prs map[string][]contributions.PullRequest, issues []issues.Issue, tasks []vikunja.Task, prompt string, openaiEndpoint string, openaiToken string, openaiModel string) (string, error) { +func SummarizeData(employeename string, prs map[string][]contributions.PullRequest, issues []issues.Issue, tasks []vikunja.Task, prompt string, openaiEndpoint string, openaiToken string, openaiModel string, summarizer Summarizer) (string, error) { // Build the prompt fullPrompt := buildPrompt(employeename, prs, issues, tasks, prompt) // Call the summarization endpoint only if OpenAI env vars are set if openaiEndpoint != "" && openaiToken != "" { - result, err := callSummarizationEndpoint(fullPrompt, openaiEndpoint, openaiToken, openaiModel) + result, err := summarizer.Summarize(fullPrompt, openaiEndpoint, openaiToken, openaiModel) if err != nil { return "", err }