Files
Enterprise-Knowledge-Base/internal/application/service/llmcontext/context_manager.go
T
wizardchen 266b9a5943 feat: Enhance chat and knowledge base functionalities with multi-KB support
- Updated agentChat API to accept optional knowledge_base_ids and agent_enabled parameters.
- Introduced getSession API to retrieve session details.
- Modified useStream to validate knowledge_base_ids for agent-chat requests and prepare POST body accordingly.
- Enhanced createKnowledgeBase API to include additional configuration options.
- Refactored chat routing to remove knowledge base ID from the chat path, allowing for dynamic knowledge base selection.
- Improved session management by preventing duplicate session entries in the menu store.
- Updated UI components for better session handling and display.
- Added context management for agent conversations to improve state persistence across sessions.
2025-11-06 21:12:26 +08:00

157 lines
6.3 KiB
Go

package llmcontext
import (
"context"
"fmt"
"github.com/Tencent/WeKnora/internal/logger"
"github.com/Tencent/WeKnora/internal/models/chat"
"github.com/Tencent/WeKnora/internal/types/interfaces"
)
// contextManager implements the ContextManager interface
// It handles business logic (compression, token management) and delegates storage to ContextStorage
type contextManager struct {
storage ContextStorage // Storage backend (Redis, Memory, etc.)
compressionStrategy interfaces.CompressionStrategy // Compression strategy
maxTokens int // Maximum tokens allowed in context
}
// NewContextManager creates a new context manager with the specified storage and compression strategy
func NewContextManager(storage ContextStorage, compressionStrategy interfaces.CompressionStrategy, maxTokens int) interfaces.ContextManager {
return &contextManager{
storage: storage,
compressionStrategy: compressionStrategy,
maxTokens: maxTokens,
}
}
// NewContextManagerWithMemory creates a context manager with in-memory storage (for backward compatibility)
func NewContextManagerWithMemory(compressionStrategy interfaces.CompressionStrategy, maxTokens int) interfaces.ContextManager {
return &contextManager{
storage: NewMemoryStorage(),
compressionStrategy: compressionStrategy,
maxTokens: maxTokens,
}
}
// AddMessage adds a message to the session context
// This method handles the business logic: loading, appending, compression, and saving
func (cm *contextManager) AddMessage(ctx context.Context, sessionID string, message chat.Message) error {
logger.Infof(ctx, "[ContextManager][Session-%s] Adding message: role=%s, content_length=%d",
sessionID, message.Role, len(message.Content))
// Log message content preview
contentPreview := message.Content
if len(contentPreview) > 200 {
contentPreview = contentPreview[:200] + "..."
}
logger.Debugf(ctx, "[ContextManager][Session-%s] Message content preview: %s", sessionID, contentPreview)
// Load existing messages from storage
messages, err := cm.storage.Load(ctx, sessionID)
if err != nil {
logger.Errorf(ctx, "[ContextManager][Session-%s] Failed to load context: %v", sessionID, err)
return fmt.Errorf("failed to load context: %w", err)
}
// Add new message
beforeCount := len(messages)
messages = append(messages, message)
logger.Debugf(ctx, "[ContextManager][Session-%s] Messages count: %d -> %d", sessionID, beforeCount, len(messages))
// Check if compression is needed
tokenCount := cm.compressionStrategy.EstimateTokens(messages)
logger.Debugf(ctx, "[ContextManager][Session-%s] Current token count: %d (max: %d)",
sessionID, tokenCount, cm.maxTokens)
if tokenCount > cm.maxTokens {
logger.Infof(ctx, "[ContextManager][Session-%s] Context exceeds max tokens (%d > %d), applying compression",
sessionID, tokenCount, cm.maxTokens)
beforeCompressionCount := len(messages)
compressed, err := cm.compressionStrategy.Compress(ctx, messages, cm.maxTokens)
if err != nil {
logger.Errorf(ctx, "[ContextManager][Session-%s] Failed to compress context: %v", sessionID, err)
return fmt.Errorf("failed to compress context: %w", err)
}
messages = compressed
afterTokenCount := cm.compressionStrategy.EstimateTokens(messages)
logger.Infof(ctx, "[ContextManager][Session-%s] Context compressed: %d -> %d messages, %d -> %d tokens",
sessionID, beforeCompressionCount, len(compressed), tokenCount, afterTokenCount)
}
// Save updated messages to storage
if err := cm.storage.Save(ctx, sessionID, messages); err != nil {
logger.Errorf(ctx, "[ContextManager][Session-%s] Failed to save context: %v", sessionID, err)
return fmt.Errorf("failed to save context: %w", err)
}
logger.Infof(ctx, "[ContextManager][Session-%s] Successfully added message (total: %d messages)", sessionID, len(messages))
return nil
}
// GetContext retrieves the current context for a session from storage
func (cm *contextManager) GetContext(ctx context.Context, sessionID string) ([]chat.Message, error) {
logger.Infof(ctx, "[ContextManager][Session-%s] Getting context", sessionID)
// Load messages from storage
messages, err := cm.storage.Load(ctx, sessionID)
if err != nil {
logger.Errorf(ctx, "[ContextManager][Session-%s] Failed to load context: %v", sessionID, err)
return nil, fmt.Errorf("failed to load context: %w", err)
}
// Calculate token estimate
tokenCount := cm.compressionStrategy.EstimateTokens(messages)
logger.Infof(ctx, "[ContextManager][Session-%s] Retrieved %d messages (~%d tokens)",
sessionID, len(messages), tokenCount)
// Log message role distribution
roleCount := make(map[string]int)
for _, msg := range messages {
roleCount[msg.Role]++
}
logger.Debugf(ctx, "[ContextManager][Session-%s] Message distribution: %v", sessionID, roleCount)
return messages, nil
}
// ClearContext clears all context for a session from storage
func (cm *contextManager) ClearContext(ctx context.Context, sessionID string) error {
logger.Infof(ctx, "[ContextManager][Session-%s] Clearing context", sessionID)
// Delete from storage
if err := cm.storage.Delete(ctx, sessionID); err != nil {
logger.Errorf(ctx, "[ContextManager][Session-%s] Failed to clear context: %v", sessionID, err)
return fmt.Errorf("failed to clear context: %w", err)
}
logger.Infof(ctx, "[ContextManager][Session-%s] Context cleared successfully", sessionID)
return nil
}
// GetContextStats returns statistics about the context
func (cm *contextManager) GetContextStats(ctx context.Context, sessionID string) (*interfaces.ContextStats, error) {
// Load messages from storage
messages, err := cm.storage.Load(ctx, sessionID)
if err != nil {
logger.Errorf(ctx, "[ContextManager][Session-%s] Failed to load context for stats: %v", sessionID, err)
return nil, fmt.Errorf("failed to load context: %w", err)
}
tokenCount := cm.compressionStrategy.EstimateTokens(messages)
stats := &interfaces.ContextStats{
MessageCount: len(messages),
TokenCount: tokenCount,
IsCompressed: false, // We'd need to track this explicitly for accurate reporting
OriginalMessageCount: len(messages),
}
logger.Debugf(ctx, "[ContextManager][Session-%s] Context stats: %d messages, ~%d tokens",
sessionID, stats.MessageCount, stats.TokenCount)
return stats, nil
}