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 <noreply@anthropic.com>
This commit is contained in:
parent
ec7b98708d
commit
b9a0677572
6 changed files with 65 additions and 52 deletions
|
|
@ -218,14 +218,16 @@ func (cb *ContextBuilder) BuildMessages(
|
||||||
) []providers.Message {
|
) []providers.Message {
|
||||||
messages := []providers.Message{}
|
messages := []providers.Message{}
|
||||||
|
|
||||||
systemPrompt := cb.BuildSystemPrompt()
|
var sysBuilder strings.Builder
|
||||||
|
sysBuilder.WriteString(cb.BuildSystemPrompt())
|
||||||
|
|
||||||
// Add Current Session info if provided
|
// Add Current Session info if provided
|
||||||
if channel != "" && chatID != "" {
|
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)
|
// Log system prompt summary for debugging (debug mode only)
|
||||||
|
systemPrompt := sysBuilder.String()
|
||||||
logger.DebugCF("agent", "System prompt built",
|
logger.DebugCF("agent", "System prompt built",
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"total_chars": len(systemPrompt),
|
"total_chars": len(systemPrompt),
|
||||||
|
|
@ -244,7 +246,9 @@ func (cb *ContextBuilder) BuildMessages(
|
||||||
})
|
})
|
||||||
|
|
||||||
if summary != "" {
|
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)
|
history = sanitizeHistoryForProvider(history)
|
||||||
|
|
|
||||||
|
|
@ -228,8 +228,8 @@ func (c *SlackChannel) handleMessageEvent(ev *slackevents.MessageEvent) {
|
||||||
Timestamp: messageTS,
|
Timestamp: messageTS,
|
||||||
})
|
})
|
||||||
|
|
||||||
content := ev.Text
|
var contentBuf strings.Builder
|
||||||
content = c.stripBotMention(content)
|
contentBuf.WriteString(c.stripBotMention(ev.Text))
|
||||||
|
|
||||||
var mediaPaths []string
|
var mediaPaths []string
|
||||||
localFiles := []string{} // 跟踪需要清理的本地文件
|
localFiles := []string{} // 跟踪需要清理的本地文件
|
||||||
|
|
@ -262,16 +262,17 @@ func (c *SlackChannel) handleMessageEvent(ev *slackevents.MessageEvent) {
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.ErrorCF("slack", "Voice transcription failed", map[string]any{"error": err.Error()})
|
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 {
|
} else {
|
||||||
content += fmt.Sprintf("\n[voice transcription: %s]", result.Text)
|
fmt.Fprintf(&contentBuf, "\n[voice transcription: %s]", result.Text)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
content += fmt.Sprintf("\n[file: %s]", file.Name)
|
fmt.Fprintf(&contentBuf, "\n[file: %s]", file.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
content := contentBuf.String()
|
||||||
if strings.TrimSpace(content) == "" {
|
if strings.TrimSpace(content) == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -797,7 +798,7 @@ func extractCodeBlocks(text string) codeBlockMatch {
|
||||||
|
|
||||||
i := 0
|
i := 0
|
||||||
text = re.ReplaceAllStringFunc(text, func(m string) string {
|
text = re.ReplaceAllStringFunc(text, func(m string) string {
|
||||||
placeholder := fmt.Sprintf("\x00CB%d\x00", i)
|
placeholder := "\x00CB" + strconv.Itoa(i) + "\x00"
|
||||||
i++
|
i++
|
||||||
return placeholder
|
return placeholder
|
||||||
})
|
})
|
||||||
|
|
@ -821,7 +822,7 @@ func extractInlineCodes(text string) inlineCodeMatch {
|
||||||
|
|
||||||
i := 0
|
i := 0
|
||||||
text = re.ReplaceAllStringFunc(text, func(m string) string {
|
text = re.ReplaceAllStringFunc(text, func(m string) string {
|
||||||
placeholder := fmt.Sprintf("\x00IC%d\x00", i)
|
placeholder := "\x00IC" + strconv.Itoa(i) + "\x00"
|
||||||
i++
|
i++
|
||||||
return placeholder
|
return placeholder
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -239,11 +239,18 @@ func formatComponent(component string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatFields(fields map[string]any) string {
|
func formatFields(fields map[string]any) string {
|
||||||
var parts []string
|
var sb strings.Builder
|
||||||
|
sb.WriteByte('{')
|
||||||
|
first := true
|
||||||
for k, v := range fields {
|
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) {
|
func Debug(message string) {
|
||||||
|
|
|
||||||
|
|
@ -214,15 +214,20 @@ func (sl *SkillsLoader) LoadSkillsForContext(skillNames []string) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
var parts []string
|
var sb strings.Builder
|
||||||
|
first := true
|
||||||
for _, name := range skillNames {
|
for _, name := range skillNames {
|
||||||
content, ok := sl.LoadSkill(name)
|
content, ok := sl.LoadSkill(name)
|
||||||
if ok {
|
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 {
|
func (sl *SkillsLoader) BuildSkillsSummary() string {
|
||||||
|
|
@ -231,23 +236,23 @@ func (sl *SkillsLoader) BuildSkillsSummary() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
var lines []string
|
var sb strings.Builder
|
||||||
lines = append(lines, "<skills>")
|
sb.WriteString("<skills>")
|
||||||
for _, s := range allSkills {
|
for _, s := range allSkills {
|
||||||
escapedName := escapeXML(s.Name)
|
escapedName := escapeXML(s.Name)
|
||||||
escapedDesc := escapeXML(s.Description)
|
escapedDesc := escapeXML(s.Description)
|
||||||
escapedPath := escapeXML(s.Path)
|
escapedPath := escapeXML(s.Path)
|
||||||
|
|
||||||
lines = append(lines, fmt.Sprintf(" <skill>"))
|
sb.WriteString("\n <skill>")
|
||||||
lines = append(lines, fmt.Sprintf(" <name>%s</name>", escapedName))
|
fmt.Fprintf(&sb, "\n <name>%s</name>", escapedName)
|
||||||
lines = append(lines, fmt.Sprintf(" <description>%s</description>", escapedDesc))
|
fmt.Fprintf(&sb, "\n <description>%s</description>", escapedDesc)
|
||||||
lines = append(lines, fmt.Sprintf(" <location>%s</location>", escapedPath))
|
fmt.Fprintf(&sb, "\n <location>%s</location>", escapedPath)
|
||||||
lines = append(lines, fmt.Sprintf(" <source>%s</source>", s.Source))
|
fmt.Fprintf(&sb, "\n <source>%s</source>", s.Source)
|
||||||
lines = append(lines, " </skill>")
|
sb.WriteString("\n </skill>")
|
||||||
}
|
}
|
||||||
lines = append(lines, "</skills>")
|
sb.WriteString("\n</skills>")
|
||||||
|
|
||||||
return strings.Join(lines, "\n")
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sl *SkillsLoader) getSkillMetadata(skillPath string) *SkillMetadata {
|
func (sl *SkillsLoader) getSkillMetadata(skillPath string) *SkillMetadata {
|
||||||
|
|
|
||||||
|
|
@ -70,19 +70,19 @@ func (p *BraveSearchProvider) Search(ctx context.Context, query string, count in
|
||||||
return fmt.Sprintf("No results for: %s", query), nil
|
return fmt.Sprintf("No results for: %s", query), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var lines []string
|
var sb strings.Builder
|
||||||
lines = append(lines, fmt.Sprintf("Results for: %s", query))
|
fmt.Fprintf(&sb, "Results for: %s", query)
|
||||||
for i, item := range results {
|
for i, item := range results {
|
||||||
if i >= count {
|
if i >= count {
|
||||||
break
|
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 != "" {
|
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 {
|
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
|
return fmt.Sprintf("No results for: %s", query), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var lines []string
|
var sb strings.Builder
|
||||||
lines = append(lines, fmt.Sprintf("Results for: %s (via Tavily)", query))
|
fmt.Fprintf(&sb, "Results for: %s (via Tavily)", query)
|
||||||
for i, item := range results {
|
for i, item := range results {
|
||||||
if i >= count {
|
if i >= count {
|
||||||
break
|
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 != "" {
|
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{}
|
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
|
return fmt.Sprintf("No results found or extraction failed. Query: %s", query), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var lines []string
|
var sb strings.Builder
|
||||||
lines = append(lines, fmt.Sprintf("Results for: %s (via DuckDuckGo)", query))
|
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(`<a class="result__snippet[^"]*".*?>([\s\S]*?)</a>`)
|
reSnippet := regexp.MustCompile(`<a class="result__snippet[^"]*".*?>([\s\S]*?)</a>`)
|
||||||
snippetMatches := reSnippet.FindAllStringSubmatch(html, count+5)
|
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
|
// Attempt to attach snippet if available and index aligns
|
||||||
if i < len(snippetMatches) {
|
if i < len(snippetMatches) {
|
||||||
snippet := stripTags(snippetMatches[i][1])
|
snippet := stripTags(snippetMatches[i][1])
|
||||||
snippet = strings.TrimSpace(snippet)
|
snippet = strings.TrimSpace(snippet)
|
||||||
if 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 {
|
func stripTags(content string) string {
|
||||||
|
|
@ -605,13 +597,16 @@ func (t *WebFetchTool) extractText(htmlContent string) string {
|
||||||
result = re.ReplaceAllString(result, "\n\n")
|
result = re.ReplaceAllString(result, "\n\n")
|
||||||
|
|
||||||
lines := strings.Split(result, "\n")
|
lines := strings.Split(result, "\n")
|
||||||
var cleanLines []string
|
var sb strings.Builder
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
line = strings.TrimSpace(line)
|
line = strings.TrimSpace(line)
|
||||||
if 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()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue