summaryrefslogtreecommitdiffhomepage
path: root/internal/llm
diff options
context:
space:
mode:
Diffstat (limited to 'internal/llm')
-rw-r--r--internal/llm/agent/agent.go82
1 files changed, 74 insertions, 8 deletions
diff --git a/internal/llm/agent/agent.go b/internal/llm/agent/agent.go
index 8e744f2d1..6023a21aa 100644
--- a/internal/llm/agent/agent.go
+++ b/internal/llm/agent/agent.go
@@ -47,7 +47,9 @@ type Service interface {
IsSessionBusy(sessionID string) bool
IsBusy() bool
Update(agentName config.AgentName, modelID models.ModelID) (models.Model, error)
- CompactSession(ctx context.Context, sessionID string) error
+ CompactSession(ctx context.Context, sessionID string, force bool) error
+ GetUsage(ctx context.Context, sessionID string) (*int64, error)
+ EstimateContextWindowUsage(ctx context.Context, sessionID string) (float64, bool, error)
}
type agent struct {
@@ -194,17 +196,16 @@ func (a *agent) Run(ctx context.Context, sessionID string, content string, attac
events <- result
close(events)
}()
+
return events, nil
}
func (a *agent) prepareMessageHistory(ctx context.Context, sessionID string) (session.Session, []message.Message, error) {
- // Get the current session to check for summary
currentSession, err := a.sessions.Get(ctx, sessionID)
if err != nil {
return currentSession, nil, fmt.Errorf("failed to get session: %w", err)
}
- // Fetch messages based on whether a summary exists
var sessionMessages []message.Message
if currentSession.Summary != "" && currentSession.SummarizedAt > 0 {
// If summary exists, only fetch messages after the summarization timestamp
@@ -220,7 +221,6 @@ func (a *agent) prepareMessageHistory(ctx context.Context, sessionID string) (se
}
}
- // Prepare the message history for the LLM
var messages []message.Message
if currentSession.Summary != "" && currentSession.SummarizedAt > 0 {
// If summary exists, create a temporary message for the summary
@@ -253,7 +253,6 @@ func (a *agent) triggerTitleGeneration(sessionID string, content string) {
}
func (a *agent) processGeneration(ctx context.Context, sessionID, content string, attachmentParts []message.ContentPart) AgentEvent {
- // Get message history and session info
currentSession, sessionMessages, err := a.prepareMessageHistory(ctx, sessionID)
if err != nil {
return a.err(err)
@@ -269,7 +268,6 @@ func (a *agent) processGeneration(ctx context.Context, sessionID, content string
return a.err(fmt.Errorf("failed to create user message: %w", err))
}
- // Append the new user message to the conversation history
messages := append(sessionMessages, userMsg)
for {
@@ -280,6 +278,41 @@ func (a *agent) processGeneration(ctx context.Context, sessionID, content string
default:
// Continue processing
}
+
+ // Check if auto-compaction is needed before calling the provider
+ usagePercentage, needsCompaction, errEstimate := a.EstimateContextWindowUsage(ctx, sessionID)
+ if errEstimate != nil {
+ slog.Warn("Failed to estimate context window usage for auto-compaction", "error", errEstimate, "sessionID", sessionID)
+ } else if needsCompaction {
+ status.Info(fmt.Sprintf("Context window usage is at %.2f%%. Auto-compacting conversation...", usagePercentage))
+
+ // Run compaction synchronously
+ compactCtx, cancelCompact := context.WithTimeout(ctx, 30*time.Second) // Use appropriate context
+ errCompact := a.CompactSession(compactCtx, sessionID, true)
+ cancelCompact()
+
+ if errCompact != nil {
+ status.Warn(fmt.Sprintf("Auto-compaction failed: %v. Context window usage may continue to grow.", errCompact))
+ } else {
+ status.Info("Auto-compaction completed successfully.")
+ // After compaction, message history needs to be re-prepared.
+ // The 'messages' slice needs to be updated with the new summary and subsequent messages,
+ // ensuring the latest user message is correctly appended.
+ _, sessionMessagesFromCompact, errPrepare := a.prepareMessageHistory(ctx, sessionID)
+ if errPrepare != nil {
+ return a.err(fmt.Errorf("failed to re-prepare message history after compaction: %w", errPrepare))
+ }
+ messages = sessionMessagesFromCompact
+
+ // Ensure the user message that triggered this cycle is the last one.
+ // 'userMsg' was created before this loop using a.createUserMessage.
+ // It should be appended to the 'messages' slice if it's not already the last element.
+ if len(messages) == 0 || (len(messages) > 0 && messages[len(messages)-1].ID != userMsg.ID) {
+ messages = append(messages, userMsg)
+ }
+ }
+ }
+
agentMessage, toolResults, err := a.streamAndHandleEvents(ctx, sessionID, messages)
if err != nil {
if errors.Is(err, context.Canceled) {
@@ -531,6 +564,39 @@ func (a *agent) GetUsage(ctx context.Context, sessionID string) (*int64, error)
return &usage, nil
}
+func (a *agent) EstimateContextWindowUsage(ctx context.Context, sessionID string) (float64, bool, error) {
+ session, err := a.sessions.Get(ctx, sessionID)
+ if err != nil {
+ return 0, false, fmt.Errorf("failed to get session: %w", err)
+ }
+
+ // Get the model's context window size
+ model := a.provider.Model()
+ contextWindow := model.ContextWindow
+ if contextWindow <= 0 {
+ // Default to a reasonable size if not specified
+ contextWindow = 100000
+ }
+
+ // Calculate current token usage
+ currentTokens := session.PromptTokens + session.CompletionTokens
+
+ // Get the max tokens setting for the agent
+ maxTokens := a.provider.MaxTokens()
+
+ // Calculate percentage of context window used
+ usagePercentage := float64(currentTokens) / float64(contextWindow)
+
+ // Check if we need to auto-compact
+ // Auto-compact when:
+ // 1. Usage exceeds 90% of context window, OR
+ // 2. Current usage + maxTokens would exceed 100% of context window
+ needsCompaction := usagePercentage >= 0.9 ||
+ float64(currentTokens+maxTokens) > float64(contextWindow)
+
+ return usagePercentage * 100, needsCompaction, nil
+}
+
func (a *agent) TrackUsage(ctx context.Context, sessionID string, model models.Model, usage provider.TokenUsage) error {
sess, err := a.sessions.Get(ctx, sessionID)
if err != nil {
@@ -572,9 +638,9 @@ func (a *agent) Update(agentName config.AgentName, modelID models.ModelID) (mode
return a.provider.Model(), nil
}
-func (a *agent) CompactSession(ctx context.Context, sessionID string) error {
+func (a *agent) CompactSession(ctx context.Context, sessionID string, force bool) error {
// Check if the session is busy
- if a.IsSessionBusy(sessionID) {
+ if a.IsSessionBusy(sessionID) && !force {
return ErrSessionBusy
}