Enhance company service with AI recommendation caching and onboarding improvements
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
- Updated the `companyService` to include Redis caching for AI recommendations, improving performance and reducing redundant AI calls. - Introduced asynchronous AI onboarding triggered after company profile updates, enhancing user experience by offloading processing. - Added configuration for recommendation cache TTL in the `AISummarizerConfig`, allowing for flexible cache management. - Implemented methods for caching, retrieving, and invalidating AI recommendations in the `companyService`, ensuring efficient data handling. This update enhances the company's AI recommendation capabilities, providing faster responses and a more efficient onboarding process.
This commit is contained in:
@@ -9,11 +9,19 @@ import (
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
|
||||
"tm/pkg/ai_summarizer"
|
||||
)
|
||||
|
||||
var ErrAIRecommendationNotConfigured = errors.New("AI recommendation service is not configured")
|
||||
|
||||
const aiRecommendationCacheKeyPrefix = "ai:recommendations:"
|
||||
|
||||
func aiRecommendationCacheKey(companyID string) string {
|
||||
return aiRecommendationCacheKeyPrefix + companyID
|
||||
}
|
||||
|
||||
// AIRecommendationClient defines the interface for company tender recommendation AI operations.
|
||||
type AIRecommendationClient interface {
|
||||
StartOnboarding(ctx context.Context, reqBody ai_summarizer.OnboardingRequest) (*ai_summarizer.OnboardingResponse, error)
|
||||
@@ -77,18 +85,56 @@ func (s *companyService) StartAIOnboarding(ctx context.Context, companyID string
|
||||
return nil, fmt.Errorf("failed to start AI onboarding: %w", err)
|
||||
}
|
||||
|
||||
s.invalidateAIRecommendationCache(ctx, companyID)
|
||||
|
||||
return &OnboardingResponse{Status: result.Status}, nil
|
||||
}
|
||||
|
||||
// triggerAIOnboardingAsync starts AI onboarding in the background after company profile changes.
|
||||
func (s *companyService) triggerAIOnboardingAsync(companyID string) {
|
||||
if s.aiRecommendationClient == nil {
|
||||
return
|
||||
}
|
||||
|
||||
companyID = strings.TrimSpace(companyID)
|
||||
if companyID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
ctx := context.Background()
|
||||
s.logger.Info("Starting async AI company onboarding", map[string]interface{}{
|
||||
"company_id": companyID,
|
||||
})
|
||||
|
||||
if _, err := s.StartAIOnboarding(ctx, companyID); err != nil {
|
||||
s.logger.Error("Async AI company onboarding failed", map[string]interface{}{
|
||||
"company_id": companyID,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Info("Async AI company onboarding completed", map[string]interface{}{
|
||||
"company_id": companyID,
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
// GetAIRecommendations retrieves ranked tender recommendations from the AI team.
|
||||
func (s *companyService) GetAIRecommendations(ctx context.Context, companyID string) ([]RecommendedTenderResponse, error) {
|
||||
if s.aiRecommendationClient == nil {
|
||||
return nil, ErrAIRecommendationNotConfigured
|
||||
}
|
||||
if strings.TrimSpace(companyID) == "" {
|
||||
companyID = strings.TrimSpace(companyID)
|
||||
if companyID == "" {
|
||||
return nil, errors.New("company ID is required")
|
||||
}
|
||||
|
||||
if cached, ok := s.getCachedAIRecommendations(ctx, companyID); ok {
|
||||
return cached, nil
|
||||
}
|
||||
|
||||
if _, err := s.repository.GetByID(ctx, companyID); err != nil {
|
||||
s.logger.Error("Failed to load company for AI recommendations", map[string]interface{}{
|
||||
"company_id": companyID,
|
||||
@@ -121,9 +167,80 @@ func (s *companyService) GetAIRecommendations(ctx context.Context, companyID str
|
||||
})
|
||||
}
|
||||
|
||||
s.cacheAIRecommendations(ctx, companyID, responses)
|
||||
|
||||
return responses, nil
|
||||
}
|
||||
|
||||
func (s *companyService) getCachedAIRecommendations(ctx context.Context, companyID string) ([]RecommendedTenderResponse, bool) {
|
||||
if s.redisClient == nil || s.recommendationCacheTTL <= 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
raw, err := s.redisClient.Get(ctx, aiRecommendationCacheKey(companyID))
|
||||
if err != nil {
|
||||
if err != redis.Nil {
|
||||
s.logger.Warn("Failed to read AI recommendation cache", map[string]interface{}{
|
||||
"company_id": companyID,
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
var responses []RecommendedTenderResponse
|
||||
if err := json.Unmarshal([]byte(raw), &responses); err != nil {
|
||||
s.logger.Warn("Failed to decode AI recommendation cache", map[string]interface{}{
|
||||
"company_id": companyID,
|
||||
"error": err.Error(),
|
||||
})
|
||||
_ = s.redisClient.Del(ctx, aiRecommendationCacheKey(companyID))
|
||||
return nil, false
|
||||
}
|
||||
|
||||
s.logger.Debug("AI tender recommendations served from cache", map[string]interface{}{
|
||||
"company_id": companyID,
|
||||
"count": len(responses),
|
||||
})
|
||||
|
||||
return responses, true
|
||||
}
|
||||
|
||||
func (s *companyService) cacheAIRecommendations(ctx context.Context, companyID string, responses []RecommendedTenderResponse) {
|
||||
if s.redisClient == nil || s.recommendationCacheTTL <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
encoded, err := json.Marshal(responses)
|
||||
if err != nil {
|
||||
s.logger.Warn("Failed to encode AI recommendations for cache", map[string]interface{}{
|
||||
"company_id": companyID,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.redisClient.Set(ctx, aiRecommendationCacheKey(companyID), string(encoded), s.recommendationCacheTTL); err != nil {
|
||||
s.logger.Warn("Failed to store AI recommendation cache", map[string]interface{}{
|
||||
"company_id": companyID,
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *companyService) invalidateAIRecommendationCache(ctx context.Context, companyID string) {
|
||||
if s.redisClient == nil || s.recommendationCacheTTL <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.redisClient.Del(ctx, aiRecommendationCacheKey(companyID)); err != nil {
|
||||
s.logger.Warn("Failed to invalidate AI recommendation cache", map[string]interface{}{
|
||||
"company_id": companyID,
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *companyService) buildOnboardingDocuments(fileIDs []string) (string, error) {
|
||||
if len(fileIDs) == 0 {
|
||||
return "[]", nil
|
||||
|
||||
Reference in New Issue
Block a user