added AI auto-generated keywords to customer profile

This commit is contained in:
Mazyar
2026-01-04 13:44:45 +03:30
parent 3381e04e05
commit 8dcda0e031
9 changed files with 435 additions and 48 deletions
+212 -4
View File
@@ -11,6 +11,7 @@ import (
"time"
"tm/internal/company"
"tm/pkg/glm"
"tm/pkg/logger"
"tm/pkg/notification"
"tm/pkg/redis"
@@ -61,10 +62,11 @@ type customerService struct {
redisClient redis.Client
notification notification.SDK
validator ValidationService
glmSDK *glm.SDK
}
// New creates a new customer service
func NewService(repository Repository, logger logger.Logger, authService authorization.AuthorizationService, companyService company.Service, redisClient redis.Client, notificationSDK notification.SDK, validator ValidationService) Service {
func NewService(repository Repository, logger logger.Logger, authService authorization.AuthorizationService, companyService company.Service, redisClient redis.Client, notificationSDK notification.SDK, validator ValidationService, glmSDK *glm.SDK) Service {
return &customerService{
repository: repository,
logger: logger,
@@ -73,6 +75,7 @@ func NewService(repository Repository, logger logger.Logger, authService authori
redisClient: redisClient,
notification: notificationSDK,
validator: validator,
glmSDK: glmSDK,
}
}
@@ -127,6 +130,21 @@ func (s *customerService) Register(ctx context.Context, form *CreateCustomerForm
Phone: form.Phone,
}
// Generate keywords using GLM AI
customer.KeywordsStatus = KeywordsStatusInProgress
keywords, err := s.generateKeywords(ctx, customer)
if err != nil {
s.logger.Warn("Failed to generate keywords, continuing without keywords", map[string]interface{}{
"error": err.Error(),
"customer_id": customer.ID,
})
keywords = []string{}
customer.KeywordsStatus = KeywordsStatusFailed
} else {
customer.Keywords = keywords
customer.KeywordsStatus = KeywordsStatusReady
}
// Save to database
err = s.repository.Register(ctx, customer)
if err != nil {
@@ -335,7 +353,24 @@ func (s *customerService) Update(ctx context.Context, id string, form *UpdateCus
// Handle company assignments
companies := make([]*CompanySummary, 0)
companiesChanged := false
if len(form.Companies) > 0 {
// Check if companies changed
if len(form.Companies) != len(customer.Companies) {
companiesChanged = true
} else {
companyMap := make(map[string]bool)
for _, id := range customer.Companies {
companyMap[id] = true
}
for _, id := range form.Companies {
if !companyMap[id] {
companiesChanged = true
break
}
}
}
customer.Companies = form.Companies
companies, err = s.loadCompaniesForCustomer(ctx, customer)
if err != nil {
s.logger.Error("Failed to load companies for customer", map[string]interface{}{
@@ -345,17 +380,101 @@ func (s *customerService) Update(ctx context.Context, id string, form *UpdateCus
}
}
if form.FullName != nil {
infoChanged := false
if form.FullName != nil && (customer.FullName == nil || *form.FullName != *customer.FullName) {
customer.FullName = form.FullName
infoChanged = true
}
if form.Phone != nil {
if form.Phone != nil && (customer.Phone == nil || *form.Phone != *customer.Phone) {
customer.Phone = form.Phone
infoChanged = true
}
// Update role if provided
if form.Role != "" {
if form.Role != "" && customer.Role != CustomerRole(form.Role) {
customer.Role = CustomerRole(form.Role)
infoChanged = true
}
// Handle keywords
if form.Keywords != nil {
// Keywords explicitly provided by customer, set status to in_progress
// Customer's manual keywords are saved, but we'll enhance them with AI
customer.Keywords = form.Keywords
customer.KeywordsStatus = KeywordsStatusInProgress
// Save customer with in_progress status first, then process asynchronously
customerID := customer.ID.Hex()
go func() {
ctx := context.Background()
// Get fresh customer data for processing
cust, err := s.repository.GetByID(ctx, customerID)
if err != nil {
s.logger.Error("Failed to get customer for keyword processing", map[string]interface{}{
"error": err.Error(),
"customer_id": customerID,
})
return
}
// If GLM SDK is not available, keep manual keywords and set status to ready
if s.glmSDK == nil {
s.logger.Info("GLM SDK not available, keeping manual keywords", map[string]interface{}{
"customer_id": customerID,
})
cust.KeywordsStatus = KeywordsStatusReady
} else {
// Regenerate keywords based on updated customer info
keywords, err := s.generateKeywords(ctx, cust)
if err != nil {
s.logger.Warn("Failed to process keywords after manual update", map[string]interface{}{
"error": err.Error(),
"customer_id": customerID,
})
// Keep manual keywords but set status to failed
cust.KeywordsStatus = KeywordsStatusFailed
} else if len(keywords) > 0 {
// Update with AI-generated keywords
cust.Keywords = keywords
cust.KeywordsStatus = KeywordsStatusReady
} else {
// Empty keywords from GLM, keep manual keywords and set status to ready
s.logger.Warn("GLM returned empty keywords, keeping manual keywords", map[string]interface{}{
"customer_id": customerID,
})
cust.KeywordsStatus = KeywordsStatusReady
}
}
// Update customer with new status
err = s.repository.Update(ctx, cust)
if err != nil {
s.logger.Error("Failed to update customer keywords status", map[string]interface{}{
"error": err.Error(),
"customer_id": customerID,
})
} else {
s.logger.Info("Keywords processed successfully", map[string]interface{}{
"customer_id": customerID,
"status": cust.KeywordsStatus,
})
}
}()
} else if infoChanged || companiesChanged {
// Customer info changed, regenerate keywords
customer.KeywordsStatus = KeywordsStatusInProgress
keywords, err := s.generateKeywords(ctx, customer)
if err != nil {
s.logger.Warn("Failed to regenerate keywords, keeping existing keywords", map[string]interface{}{
"error": err.Error(),
"customer_id": customer.ID,
})
customer.KeywordsStatus = KeywordsStatusFailed
} else {
customer.Keywords = keywords
customer.KeywordsStatus = KeywordsStatusReady
}
}
// Update in database
@@ -1244,3 +1363,92 @@ func (s *customerService) AssignRole(ctx context.Context, customerID string, for
Success: true,
}, nil
}
// generateKeywords generates keywords for a customer using GLM AI based on their information
func (s *customerService) generateKeywords(ctx context.Context, customer *Customer) ([]string, error) {
// If GLM SDK is not available, return empty keywords
if s.glmSDK == nil {
s.logger.Warn("GLM SDK not available, skipping keyword generation", map[string]interface{}{
"customer_id": customer.ID,
})
return []string{}, nil
}
// Build customer information string for AI
var customerInfo strings.Builder
if customer.FullName != nil {
customerInfo.WriteString(fmt.Sprintf("Name: %s\n", *customer.FullName))
}
customerInfo.WriteString(fmt.Sprintf("Type: %s\n", customer.Type))
customerInfo.WriteString(fmt.Sprintf("Role: %s\n", customer.Role))
if customer.Phone != nil {
customerInfo.WriteString(fmt.Sprintf("Phone: %s\n", *customer.Phone))
}
// Get company information if available
if len(customer.Companies) > 0 {
customerInfo.WriteString("Companies: ")
for i, companyID := range customer.Companies {
comp, err := s.companyService.GetByID(ctx, companyID)
if err == nil {
if i > 0 {
customerInfo.WriteString(", ")
}
customerInfo.WriteString(comp.Name)
if comp.Industry != "" {
customerInfo.WriteString(fmt.Sprintf(" (Industry: %s)", comp.Industry))
}
}
}
customerInfo.WriteString("\n")
}
systemPrompt := `You are a keyword extraction assistant for a tender management system.
Analyze the customer information and generate 5-10 relevant keywords that would help match them with appropriate tenders.
Keywords should be:
- Relevant to tender/procurement categories
- Based on customer type, role, and company information
- Professional and specific
- Suitable for matching with tender descriptions
Return ONLY a comma-separated list of keywords, no explanations or additional text.`
userMessage := fmt.Sprintf("Generate keywords for this customer:\n\n%s", customerInfo.String())
s.logger.Info("Generating keywords using GLM AI", map[string]interface{}{
"customer_id": customer.ID,
})
response, err := s.glmSDK.QuickChatWithSystem(ctx, systemPrompt, userMessage)
if err != nil {
s.logger.Error("Failed to generate keywords using GLM AI", map[string]interface{}{
"error": err.Error(),
"customer_id": customer.ID,
})
return []string{}, fmt.Errorf("failed to generate keywords: %w", err)
}
// Parse comma-separated keywords
keywordsStr := strings.TrimSpace(response)
if keywordsStr == "" {
return []string{}, nil
}
// Split by comma and clean up
keywordList := strings.Split(keywordsStr, ",")
keywords := make([]string, 0, len(keywordList))
for _, kw := range keywordList {
kw = strings.TrimSpace(kw)
if kw != "" {
keywords = append(keywords, kw)
}
}
s.logger.Info("Keywords generated successfully", map[string]interface{}{
"customer_id": customer.ID,
"keywords": keywords,
"count": len(keywords),
})
return keywords, nil
}