fix: strip think tags from heartbeat messages and log web search provider

- Strip <think> blocks from force response path (max iterations) in
  runLLMIteration, which was missing StripThinkBlocks
- Add StripThinkBlocks safety net in heartbeat sendResponse to prevent
  LLM reasoning artifacts from leaking to users
- Add ProviderName() to WebSearchTool and log selected search provider
  (brave/perplexity/tavily/duckduckgo) at startup for diagnostics
- Show web search provider in gateway startup console output

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
dj-oyu 2026-03-01 04:52:31 +09:00
parent 57f904df28
commit 98b527c712
4 changed files with 43 additions and 7 deletions

View file

@ -80,6 +80,11 @@ func gatewayCmd(debug bool, orchestration bool, enableStats bool) error {
toolsInfo := startupInfo["tools"].(map[string]any)
skillsInfo := startupInfo["skills"].(map[string]any)
fmt.Printf(" • Tools: %d loaded\n", toolsInfo["count"])
if wsProvider, ok := toolsInfo["web_search_provider"].(string); ok {
fmt.Printf(" • Web search: %s\n", wsProvider)
} else {
fmt.Println(" • Web search: disabled")
}
fmt.Printf(" • Skills: %d/%d available\n",
skillsInfo["available"],
skillsInfo["total"])

View file

@ -240,6 +240,14 @@ func registerSharedTools(
Proxy: cfg.Tools.Web.Proxy,
}); searchTool != nil {
agent.Tools.Register(searchTool)
logger.InfoCF("agent", "Web search provider registered", map[string]any{
"agent_id": agentID,
"provider": searchTool.ProviderName(),
})
} else {
logger.WarnCF("agent", "No web search provider configured", map[string]any{
"agent_id": agentID,
})
}
agent.Tools.Register(tools.NewWebFetchToolWithProxy(50000, cfg.Tools.Web.Proxy))
@ -2775,7 +2783,7 @@ func (al *AgentLoop) runLLMIteration(
"prompt_cache_key": agent.ID,
})
if forceErr == nil && forceResp.Content != "" {
finalContent = forceResp.Content
finalContent = utils.StripThinkBlocks(forceResp.Content)
if forceResp.Usage != nil && al.stats != nil {
al.stats.RecordUsage(
forceResp.Usage.PromptTokens,
@ -2896,10 +2904,17 @@ func (al *AgentLoop) GetStartupInfo() map[string]any {
// Tools info
toolsList := agent.Tools.List()
info["tools"] = map[string]any{
toolsMap := map[string]any{
"count": len(toolsList),
"names": toolsList,
}
// Report web search provider if registered
if t, ok := agent.Tools.Get("web_search"); ok {
if wst, ok := t.(*tools.WebSearchTool); ok {
toolsMap["web_search_provider"] = wst.ProviderName()
}
}
info["tools"] = toolsMap
// Skills info
info["skills"] = agent.ContextBuilder.GetSkillsInfo()

View file

@ -21,6 +21,7 @@ import (
"github.com/sipeed/picoclaw/pkg/logger"
"github.com/sipeed/picoclaw/pkg/state"
"github.com/sipeed/picoclaw/pkg/tools"
"github.com/sipeed/picoclaw/pkg/utils"
)
const (
@ -308,8 +309,11 @@ Add your heartbeat tasks below this line:
}
}
// sendResponse sends the heartbeat response to the last channel
// sendResponse sends the heartbeat response to the last channel.
// Think blocks are stripped as a safety net to prevent LLM reasoning
// artifacts from leaking into user-facing messages.
func (hs *HeartbeatService) sendResponse(response string) {
response = utils.StripThinkBlocks(response)
hs.mu.RLock()
msgBus := hs.bus
hs.mu.RUnlock()

View file

@ -387,8 +387,14 @@ func (p *PerplexitySearchProvider) Search(ctx context.Context, query string, cou
}
type WebSearchTool struct {
provider SearchProvider
maxResults int
provider SearchProvider
providerName string
maxResults int
}
// ProviderName returns the name of the active search provider (e.g. "brave", "perplexity").
func (t *WebSearchTool) ProviderName() string {
return t.providerName
}
type WebSearchToolOptions struct {
@ -409,16 +415,19 @@ type WebSearchToolOptions struct {
func NewWebSearchTool(opts WebSearchToolOptions) *WebSearchTool {
var provider SearchProvider
var providerName string
maxResults := 5
// Priority: Perplexity > Brave > Tavily > DuckDuckGo
if opts.PerplexityEnabled && opts.PerplexityAPIKey != "" {
provider = &PerplexitySearchProvider{apiKey: opts.PerplexityAPIKey, proxy: opts.Proxy}
providerName = "perplexity"
if opts.PerplexityMaxResults > 0 {
maxResults = opts.PerplexityMaxResults
}
} else if opts.BraveEnabled && opts.BraveAPIKey != "" {
provider = &BraveSearchProvider{apiKey: opts.BraveAPIKey, proxy: opts.Proxy}
providerName = "brave"
if opts.BraveMaxResults > 0 {
maxResults = opts.BraveMaxResults
}
@ -428,11 +437,13 @@ func NewWebSearchTool(opts WebSearchToolOptions) *WebSearchTool {
baseURL: opts.TavilyBaseURL,
proxy: opts.Proxy,
}
providerName = "tavily"
if opts.TavilyMaxResults > 0 {
maxResults = opts.TavilyMaxResults
}
} else if opts.DuckDuckGoEnabled {
provider = &DuckDuckGoSearchProvider{proxy: opts.Proxy}
providerName = "duckduckgo"
if opts.DuckDuckGoMaxResults > 0 {
maxResults = opts.DuckDuckGoMaxResults
}
@ -441,8 +452,9 @@ func NewWebSearchTool(opts WebSearchToolOptions) *WebSearchTool {
}
return &WebSearchTool{
provider: provider,
maxResults: maxResults,
provider: provider,
providerName: providerName,
maxResults: maxResults,
}
}