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:
parent
e6c81dd5b3
commit
e7c53ca5df
4 changed files with 16 additions and 11 deletions
|
|
@ -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{
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue