From b889617373851e3f02e132d882c8599a6f60af84 Mon Sep 17 00:00:00 2001 From: dj-oyu <68707227+dj-oyu@users.noreply.github.com> Date: Tue, 24 Feb 2026 15:42:46 +0900 Subject: [PATCH] perf: replace []string+Join with strings.Builder in hot paths Covers web search providers (Brave/Tavily/DuckDuckGo/extractText), skills loader, logger formatFields, system prompt builder, and Slack message handler. Eliminates intermediate slice allocations. Co-Authored-By: Claude Opus 4.6 --- pkg/agent/context.go | 10 ++++++--- pkg/channels/slack.go | 11 +++++----- pkg/channels/telegram.go | 5 +++-- pkg/logger/logger.go | 13 ++++++++--- pkg/skills/loader.go | 31 +++++++++++++++----------- pkg/tools/web.go | 47 ++++++++++++++++++---------------------- 6 files changed, 65 insertions(+), 52 deletions(-) diff --git a/pkg/agent/context.go b/pkg/agent/context.go index 8df221ad2..715f3bc64 100644 --- a/pkg/agent/context.go +++ b/pkg/agent/context.go @@ -218,14 +218,16 @@ func (cb *ContextBuilder) BuildMessages( ) []providers.Message { messages := []providers.Message{} - systemPrompt := cb.BuildSystemPrompt() + var sysBuilder strings.Builder + sysBuilder.WriteString(cb.BuildSystemPrompt()) // Add Current Session info if provided if channel != "" && chatID != "" { - systemPrompt += fmt.Sprintf("\n\n## Current Session\nChannel: %s\nChat ID: %s", channel, chatID) + fmt.Fprintf(&sysBuilder, "\n\n## Current Session\nChannel: %s\nChat ID: %s", channel, chatID) } // Log system prompt summary for debugging (debug mode only) + systemPrompt := sysBuilder.String() logger.DebugCF("agent", "System prompt built", map[string]any{ "total_chars": len(systemPrompt), @@ -244,7 +246,9 @@ func (cb *ContextBuilder) BuildMessages( }) if summary != "" { - systemPrompt += "\n\n## Summary of Previous Conversation\n\n" + summary + sysBuilder.WriteString("\n\n## Summary of Previous Conversation\n\n") + sysBuilder.WriteString(summary) + systemPrompt = sysBuilder.String() } history = sanitizeHistoryForProvider(history) diff --git a/pkg/channels/slack.go b/pkg/channels/slack.go index f7359cd6d..4dde2eb29 100644 --- a/pkg/channels/slack.go +++ b/pkg/channels/slack.go @@ -228,8 +228,8 @@ func (c *SlackChannel) handleMessageEvent(ev *slackevents.MessageEvent) { Timestamp: messageTS, }) - content := ev.Text - content = c.stripBotMention(content) + var contentBuf strings.Builder + contentBuf.WriteString(c.stripBotMention(ev.Text)) var mediaPaths []string localFiles := []string{} // 跟踪需要清理的本地文件 @@ -262,16 +262,17 @@ func (c *SlackChannel) handleMessageEvent(ev *slackevents.MessageEvent) { if err != nil { logger.ErrorCF("slack", "Voice transcription failed", map[string]any{"error": err.Error()}) - content += fmt.Sprintf("\n[audio: %s (transcription failed)]", file.Name) + fmt.Fprintf(&contentBuf, "\n[audio: %s (transcription failed)]", file.Name) } else { - content += fmt.Sprintf("\n[voice transcription: %s]", result.Text) + fmt.Fprintf(&contentBuf, "\n[voice transcription: %s]", result.Text) } } else { - content += fmt.Sprintf("\n[file: %s]", file.Name) + fmt.Fprintf(&contentBuf, "\n[file: %s]", file.Name) } } } + content := contentBuf.String() if strings.TrimSpace(content) == "" { return } diff --git a/pkg/channels/telegram.go b/pkg/channels/telegram.go index 3c63a88d4..cb10e3a64 100644 --- a/pkg/channels/telegram.go +++ b/pkg/channels/telegram.go @@ -7,6 +7,7 @@ import ( "net/url" "os" "regexp" + "strconv" "strings" "sync" "time" @@ -797,7 +798,7 @@ func extractCodeBlocks(text string) codeBlockMatch { i := 0 text = re.ReplaceAllStringFunc(text, func(m string) string { - placeholder := fmt.Sprintf("\x00CB%d\x00", i) + placeholder := "\x00CB" + strconv.Itoa(i) + "\x00" i++ return placeholder }) @@ -821,7 +822,7 @@ func extractInlineCodes(text string) inlineCodeMatch { i := 0 text = re.ReplaceAllStringFunc(text, func(m string) string { - placeholder := fmt.Sprintf("\x00IC%d\x00", i) + placeholder := "\x00IC" + strconv.Itoa(i) + "\x00" i++ return placeholder }) diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index 97b6b62b1..953eb494b 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -239,11 +239,18 @@ func formatComponent(component string) string { } func formatFields(fields map[string]any) string { - var parts []string + var sb strings.Builder + sb.WriteByte('{') + first := true for k, v := range fields { - parts = append(parts, fmt.Sprintf("%s=%v", k, v)) + if !first { + sb.WriteString(", ") + } + fmt.Fprintf(&sb, "%s=%v", k, v) + first = false } - return fmt.Sprintf("{%s}", strings.Join(parts, ", ")) + sb.WriteByte('}') + return sb.String() } func Debug(message string) { diff --git a/pkg/skills/loader.go b/pkg/skills/loader.go index eb0d5f322..e71ae5201 100644 --- a/pkg/skills/loader.go +++ b/pkg/skills/loader.go @@ -214,15 +214,20 @@ func (sl *SkillsLoader) LoadSkillsForContext(skillNames []string) string { return "" } - var parts []string + var sb strings.Builder + first := true for _, name := range skillNames { content, ok := sl.LoadSkill(name) if ok { - parts = append(parts, fmt.Sprintf("### Skill: %s\n\n%s", name, content)) + if !first { + sb.WriteString("\n\n---\n\n") + } + fmt.Fprintf(&sb, "### Skill: %s\n\n%s", name, content) + first = false } } - return strings.Join(parts, "\n\n---\n\n") + return sb.String() } func (sl *SkillsLoader) BuildSkillsSummary() string { @@ -231,23 +236,23 @@ func (sl *SkillsLoader) BuildSkillsSummary() string { return "" } - var lines []string - lines = append(lines, "") + var sb strings.Builder + sb.WriteString("") for _, s := range allSkills { escapedName := escapeXML(s.Name) escapedDesc := escapeXML(s.Description) escapedPath := escapeXML(s.Path) - lines = append(lines, fmt.Sprintf(" ")) - lines = append(lines, fmt.Sprintf(" %s", escapedName)) - lines = append(lines, fmt.Sprintf(" %s", escapedDesc)) - lines = append(lines, fmt.Sprintf(" %s", escapedPath)) - lines = append(lines, fmt.Sprintf(" %s", s.Source)) - lines = append(lines, " ") + sb.WriteString("\n ") + fmt.Fprintf(&sb, "\n %s", escapedName) + fmt.Fprintf(&sb, "\n %s", escapedDesc) + fmt.Fprintf(&sb, "\n %s", escapedPath) + fmt.Fprintf(&sb, "\n %s", s.Source) + sb.WriteString("\n ") } - lines = append(lines, "") + sb.WriteString("\n") - return strings.Join(lines, "\n") + return sb.String() } func (sl *SkillsLoader) getSkillMetadata(skillPath string) *SkillMetadata { diff --git a/pkg/tools/web.go b/pkg/tools/web.go index 452e95e0f..6e177ec8d 100644 --- a/pkg/tools/web.go +++ b/pkg/tools/web.go @@ -70,19 +70,19 @@ func (p *BraveSearchProvider) Search(ctx context.Context, query string, count in return fmt.Sprintf("No results for: %s", query), nil } - var lines []string - lines = append(lines, fmt.Sprintf("Results for: %s", query)) + var sb strings.Builder + fmt.Fprintf(&sb, "Results for: %s", query) for i, item := range results { if i >= count { break } - lines = append(lines, fmt.Sprintf("%d. %s\n %s", i+1, item.Title, item.URL)) + fmt.Fprintf(&sb, "\n%d. %s\n %s", i+1, item.Title, item.URL) if item.Description != "" { - lines = append(lines, fmt.Sprintf(" %s", item.Description)) + fmt.Fprintf(&sb, "\n %s", item.Description) } } - return strings.Join(lines, "\n"), nil + return sb.String(), nil } type TavilySearchProvider struct { @@ -152,19 +152,19 @@ func (p *TavilySearchProvider) Search(ctx context.Context, query string, count i return fmt.Sprintf("No results for: %s", query), nil } - var lines []string - lines = append(lines, fmt.Sprintf("Results for: %s (via Tavily)", query)) + var sb strings.Builder + fmt.Fprintf(&sb, "Results for: %s (via Tavily)", query) for i, item := range results { if i >= count { break } - lines = append(lines, fmt.Sprintf("%d. %s\n %s", i+1, item.Title, item.URL)) + fmt.Fprintf(&sb, "\n%d. %s\n %s", i+1, item.Title, item.URL) if item.Content != "" { - lines = append(lines, fmt.Sprintf(" %s", item.Content)) + fmt.Fprintf(&sb, "\n %s", item.Content) } } - return strings.Join(lines, "\n"), nil + return sb.String(), nil } type DuckDuckGoSearchProvider struct{} @@ -208,17 +208,9 @@ func (p *DuckDuckGoSearchProvider) extractResults(html string, count int, query return fmt.Sprintf("No results found or extraction failed. Query: %s", query), nil } - var lines []string - lines = append(lines, fmt.Sprintf("Results for: %s (via DuckDuckGo)", query)) + var sb strings.Builder + fmt.Fprintf(&sb, "Results for: %s (via DuckDuckGo)", query) - // Pre-compile snippet regex to run inside the loop - // We'll search for snippets relative to the link position or just globally if needed - // But simple global search for snippets might mismatch order. - // Since we only have the raw HTML string, let's just extract snippets globally and assume order matches (risky but simple for regex) - // Or better: Let's assume the snippet follows the link in the HTML - - // A better regex approach: iterate through text and find matches in order - // But for now, let's grab all snippets too reSnippet := regexp.MustCompile(`([\s\S]*?)`) snippetMatches := reSnippet.FindAllStringSubmatch(html, count+5) @@ -239,19 +231,19 @@ func (p *DuckDuckGoSearchProvider) extractResults(html string, count int, query } } - lines = append(lines, fmt.Sprintf("%d. %s\n %s", i+1, title, urlStr)) + fmt.Fprintf(&sb, "\n%d. %s\n %s", i+1, title, urlStr) // Attempt to attach snippet if available and index aligns if i < len(snippetMatches) { snippet := stripTags(snippetMatches[i][1]) snippet = strings.TrimSpace(snippet) if snippet != "" { - lines = append(lines, fmt.Sprintf(" %s", snippet)) + fmt.Fprintf(&sb, "\n %s", snippet) } } } - return strings.Join(lines, "\n"), nil + return sb.String(), nil } func stripTags(content string) string { @@ -605,13 +597,16 @@ func (t *WebFetchTool) extractText(htmlContent string) string { result = re.ReplaceAllString(result, "\n\n") lines := strings.Split(result, "\n") - var cleanLines []string + var sb strings.Builder for _, line := range lines { line = strings.TrimSpace(line) if line != "" { - cleanLines = append(cleanLines, line) + if sb.Len() > 0 { + sb.WriteByte('\n') + } + sb.WriteString(line) } } - return strings.Join(cleanLines, "\n") + return sb.String() }