fix(tools): improve error handling and search result formatting

- focus: log warnings on DeleteKV and corrupt knowledge block instead
  of silently swallowing errors
- retrieval: include search method (keyword/semantic) in result messages
  for clearer agent context
- subagent test: respect iterator yield protocol by checking return value
This commit is contained in:
ZanzyTHEbar 2026-02-19 15:48:38 +00:00
parent e6c81dd5b3
commit e7c53ca5df
4 changed files with 16 additions and 11 deletions

View file

@ -197,8 +197,9 @@ func (t *CompleteFocusTool) Execute(ctx context.Context, args map[string]interfa
pruned := t.pruneHistory(history, state.CheckpointIndex) pruned := t.pruneHistory(history, state.CheckpointIndex)
t.sessions.SetHistory(sk, pruned) t.sessions.SetHistory(sk, pruned)
// Clean up focus state if err := t.delegate.DeleteKV(ctx, focusAgentID, focusKey); err != nil {
_ = t.delegate.DeleteKV(ctx, focusAgentID, focusKey) logger.WarnCF("focus", "failed to clean up focus state", map[string]interface{}{"error": err, "session": sk})
}
msgsBefore := len(history) msgsBefore := len(history)
msgsAfter := len(pruned) msgsAfter := len(pruned)
@ -252,7 +253,9 @@ func (t *CompleteFocusTool) appendKnowledge(ctx context.Context, sessionKey, top
kb := &KnowledgeBlock{} kb := &KnowledgeBlock{}
raw, err := t.delegate.GetKV(ctx, focusAgentID, kvKey) raw, err := t.delegate.GetKV(ctx, focusAgentID, kvKey)
if err == nil && raw != "" { if err == nil && raw != "" {
_ = jsonv2.Unmarshal([]byte(raw), kb) if uerr := jsonv2.Unmarshal([]byte(raw), kb); uerr != nil {
logger.WarnCF("focus", "corrupt knowledge block, resetting", map[string]interface{}{"error": uerr, "key": kvKey})
}
} }
kb.Entries = append(kb.Entries, KnowledgeEntry{ kb.Entries = append(kb.Entries, KnowledgeEntry{

View file

@ -66,7 +66,7 @@ func (t *KeywordSearchTool) Execute(ctx context.Context, args map[string]interfa
return ErrorResult(fmt.Sprintf("keyword search failed: %v", err)) return ErrorResult(fmt.Sprintf("keyword search failed: %v", err))
} }
return SilentResult(formatSearchResults("keyword_search", query, results)) return SilentResult(formatSearchResults("keyword", query, results))
} }
// SemanticSearchTool performs vector ANN search using embeddings. // SemanticSearchTool performs vector ANN search using embeddings.
@ -136,7 +136,7 @@ func (t *SemanticSearchTool) Execute(ctx context.Context, args map[string]interf
return ErrorResult(fmt.Sprintf("semantic search failed: %v", err)) return ErrorResult(fmt.Sprintf("semantic search failed: %v", err))
} }
return SilentResult(formatSearchResults("semantic_search", query, results)) return SilentResult(formatSearchResults("semantic", query, results))
} }
// ChunkReadTool loads full document/chunk content by ID. // ChunkReadTool loads full document/chunk content by ID.
@ -185,13 +185,13 @@ func (t *ChunkReadTool) Execute(ctx context.Context, args map[string]interface{}
return SilentResult(content) return SilentResult(content)
} }
func formatSearchResults(source, query string, results []memory.SearchResult) string { func formatSearchResults(method, query string, results []memory.SearchResult) string {
if len(results) == 0 { if len(results) == 0 {
return fmt.Sprintf("No results found for: %s", query) return fmt.Sprintf("No %s results found for: %s", method, query)
} }
var sb strings.Builder var sb strings.Builder
fmt.Fprintf(&sb, "Found %d results for '%s':\n\n", len(results), query) fmt.Fprintf(&sb, "Found %d %s results for '%s':\n\n", len(results), method, query)
for i, r := range results { for i, r := range results {
fmt.Fprintf(&sb, "%d. [%s] (score: %.2f) id=%s\n", i+1, r.Source, r.Score, r.ID) fmt.Fprintf(&sb, "%d. [%s] (score: %.2f) id=%s\n", i+1, r.Source, r.Score, r.ID)

View file

@ -55,8 +55,8 @@ func TestChunkReadTool_MissingID(t *testing.T) {
} }
func TestFormatSearchResults_Empty(t *testing.T) { func TestFormatSearchResults_Empty(t *testing.T) {
output := formatSearchResults("test", "query", nil) output := formatSearchResults("keyword", "query", nil)
assert.Contains(t, output, "No results found") assert.Contains(t, output, "No keyword results found")
} }
func TestFormatSearchResults_WithResults(t *testing.T) { func TestFormatSearchResults_WithResults(t *testing.T) {

View file

@ -39,7 +39,9 @@ func (m *MockLanguageModel) Stream(_ context.Context, call fantasy.Call) (fantas
return nil, err return nil, err
} }
return func(yield func(fantasy.StreamPart) bool) { return func(yield func(fantasy.StreamPart) bool) {
yield(fantasy.StreamPart{Type: fantasy.StreamPartTypeTextDelta, Delta: resp.Content.Text()}) if !yield(fantasy.StreamPart{Type: fantasy.StreamPartTypeTextDelta, Delta: resp.Content.Text()}) {
return
}
yield(fantasy.StreamPart{Type: fantasy.StreamPartTypeFinish, FinishReason: fantasy.FinishReasonStop}) yield(fantasy.StreamPart{Type: fantasy.StreamPartTypeFinish, FinishReason: fantasy.FinishReasonStop})
}, nil }, nil
} }