Feat issue 183 (#189)
* feat: add slash command support (e.g., /show model, /help) * style: fix code formatting * feat: implement robust context compression and error recovery with user notifications
This commit is contained in:
@@ -2,6 +2,7 @@ package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@@ -527,3 +528,99 @@ func TestToolResult_UserFacingToolDoesSendMessage(t *testing.T) {
|
||||
t.Errorf("Expected 'Command output: hello world', got: %s", response)
|
||||
}
|
||||
}
|
||||
|
||||
// failFirstMockProvider fails on the first N calls with a specific error
|
||||
type failFirstMockProvider struct {
|
||||
failures int
|
||||
currentCall int
|
||||
failError error
|
||||
successResp string
|
||||
}
|
||||
|
||||
func (m *failFirstMockProvider) Chat(ctx context.Context, messages []providers.Message, tools []providers.ToolDefinition, model string, opts map[string]interface{}) (*providers.LLMResponse, error) {
|
||||
m.currentCall++
|
||||
if m.currentCall <= m.failures {
|
||||
return nil, m.failError
|
||||
}
|
||||
return &providers.LLMResponse{
|
||||
Content: m.successResp,
|
||||
ToolCalls: []providers.ToolCall{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *failFirstMockProvider) GetDefaultModel() string {
|
||||
return "mock-fail-model"
|
||||
}
|
||||
|
||||
// TestAgentLoop_ContextExhaustionRetry verify that the agent retries on context errors
|
||||
func TestAgentLoop_ContextExhaustionRetry(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
cfg := &config.Config{
|
||||
Agents: config.AgentsConfig{
|
||||
Defaults: config.AgentDefaults{
|
||||
Workspace: tmpDir,
|
||||
Model: "test-model",
|
||||
MaxTokens: 4096,
|
||||
MaxToolIterations: 10,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
msgBus := bus.NewMessageBus()
|
||||
|
||||
// Create a provider that fails once with a context error
|
||||
contextErr := fmt.Errorf("InvalidParameter: Total tokens of image and text exceed max message tokens")
|
||||
provider := &failFirstMockProvider{
|
||||
failures: 1,
|
||||
failError: contextErr,
|
||||
successResp: "Recovered from context error",
|
||||
}
|
||||
|
||||
al := NewAgentLoop(cfg, msgBus, provider)
|
||||
|
||||
// Inject some history to simulate a full context
|
||||
sessionKey := "test-session-context"
|
||||
// Create dummy history
|
||||
history := []providers.Message{
|
||||
{Role: "system", Content: "System prompt"},
|
||||
{Role: "user", Content: "Old message 1"},
|
||||
{Role: "assistant", Content: "Old response 1"},
|
||||
{Role: "user", Content: "Old message 2"},
|
||||
{Role: "assistant", Content: "Old response 2"},
|
||||
{Role: "user", Content: "Trigger message"},
|
||||
}
|
||||
al.sessions.SetHistory(sessionKey, history)
|
||||
|
||||
// Call ProcessDirectWithChannel
|
||||
// Note: ProcessDirectWithChannel calls processMessage which will execute runLLMIteration
|
||||
response, err := al.ProcessDirectWithChannel(context.Background(), "Trigger message", sessionKey, "test", "test-chat")
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Expected success after retry, got error: %v", err)
|
||||
}
|
||||
|
||||
if response != "Recovered from context error" {
|
||||
t.Errorf("Expected 'Recovered from context error', got '%s'", response)
|
||||
}
|
||||
|
||||
// We expect 2 calls: 1st failed, 2nd succeeded
|
||||
if provider.currentCall != 2 {
|
||||
t.Errorf("Expected 2 calls (1 fail + 1 success), got %d", provider.currentCall)
|
||||
}
|
||||
|
||||
// Check final history length
|
||||
finalHistory := al.sessions.GetHistory(sessionKey)
|
||||
// We verify that the history has been modified (compressed)
|
||||
// Original length: 6
|
||||
// Expected behavior: compression drops ~50% of history (mid slice)
|
||||
// We can assert that the length is NOT what it would be without compression.
|
||||
// Without compression: 6 + 1 (new user msg) + 1 (assistant msg) = 8
|
||||
if len(finalHistory) >= 8 {
|
||||
t.Errorf("Expected history to be compressed (len < 8), got %d", len(finalHistory))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user