fix: harden controlled AI session consistency
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"senlinai-agent/backend/internal/logic/projects"
|
||||
"senlinai-agent/backend/internal/models"
|
||||
)
|
||||
@@ -21,9 +22,9 @@ var (
|
||||
)
|
||||
|
||||
type sessionGateway interface {
|
||||
CheckRateLimit(userID uint, action string, limit int, window time.Duration) error
|
||||
ReserveRateLimit(userID uint, action string, limit int, window time.Duration) error
|
||||
SelectKey(userID uint) (SelectedKey, error)
|
||||
RecordCall(userID uint, provider string, usedKeyType string, action string, status string, errText string) error
|
||||
RecordCall(database *gorm.DB, userID uint, provider string, usedKeyType string, action string, status string, errText string) error
|
||||
}
|
||||
|
||||
// SessionService 只管理项目内普通 AI 会话;它不会把上下文自动转换为任务、笔记或资料。
|
||||
@@ -66,14 +67,14 @@ func (s *SessionService) Create(userID uint, projectIdentity, title, context str
|
||||
return nil, errors.New("ai gateway is required")
|
||||
}
|
||||
|
||||
if err := s.gateway.CheckRateLimit(userID, aiSessionCreateAction, aiSessionCreateLimit, time.Hour); err != nil {
|
||||
if err := s.gateway.ReserveRateLimit(userID, aiSessionCreateAction, aiSessionCreateLimit, time.Hour); err != nil {
|
||||
if errors.Is(err, ErrAIRateLimited) {
|
||||
if auditErr := s.gateway.RecordCall(userID, "none", "none", aiSessionCreateAction, "failed", "ai_rate_limited"); auditErr != nil {
|
||||
if auditErr := s.gateway.RecordCall(models.DBService, userID, "none", "none", aiSessionCreateAction, "failed", "ai_rate_limited"); auditErr != nil {
|
||||
return nil, fmt.Errorf("record ai rate limit failure: %w", auditErr)
|
||||
}
|
||||
return nil, ErrAIRateLimited
|
||||
}
|
||||
if auditErr := s.gateway.RecordCall(userID, "none", "none", aiSessionCreateAction, "failed", "rate_limit_check_failed"); auditErr != nil {
|
||||
if auditErr := s.gateway.RecordCall(models.DBService, userID, "none", "none", aiSessionCreateAction, "failed", "rate_limit_reservation_failed"); auditErr != nil {
|
||||
return nil, fmt.Errorf("check rate limit: %v; record failure: %w", err, auditErr)
|
||||
}
|
||||
return nil, err
|
||||
@@ -85,7 +86,8 @@ func (s *SessionService) Create(userID uint, projectIdentity, title, context str
|
||||
if errors.Is(err, ErrAIKeyMissing) {
|
||||
code = "ai_key_missing"
|
||||
}
|
||||
if auditErr := s.gateway.RecordCall(userID, "none", "none", aiSessionCreateAction, "failed", code); auditErr != nil {
|
||||
provider, keyType := selectedAuditMetadata(selected)
|
||||
if auditErr := s.gateway.RecordCall(models.DBService, userID, provider, keyType, aiSessionCreateAction, "failed", code); auditErr != nil {
|
||||
return nil, fmt.Errorf("select ai key: %v; record failure: %w", err, auditErr)
|
||||
}
|
||||
return nil, err
|
||||
@@ -98,19 +100,40 @@ func (s *SessionService) Create(userID uint, projectIdentity, title, context str
|
||||
Context: context,
|
||||
Status: defaultSessionStatus,
|
||||
}
|
||||
if err := models.DBService.Create(&session).Error; err != nil {
|
||||
if auditErr := s.gateway.RecordCall(userID, selected.Provider, selected.KeyType, aiSessionCreateAction, "failed", "session_create_failed"); auditErr != nil {
|
||||
return nil, fmt.Errorf("create ai session: %v; record failure: %w", err, auditErr)
|
||||
failureCode := "session_create_failed"
|
||||
err = models.DBService.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Create(&session).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// ready 只表示会话入口已建立,不表示 provider 已回复或任何业务对象已创建。
|
||||
failureCode = "audit_write_failed"
|
||||
if err := s.gateway.RecordCall(tx, userID, selected.Provider, selected.KeyType, aiSessionCreateAction, defaultSessionStatus, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
failureCode = "session_transaction_failed"
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
if auditErr := s.gateway.RecordCall(models.DBService, userID, selected.Provider, selected.KeyType, aiSessionCreateAction, "failed", failureCode); auditErr != nil {
|
||||
return nil, fmt.Errorf("create ai session transaction: %v; record failure: %w", err, auditErr)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
// ready 只表示会话入口已建立,不表示 provider 已回复或任何业务对象已创建。
|
||||
if err := s.gateway.RecordCall(userID, selected.Provider, selected.KeyType, aiSessionCreateAction, defaultSessionStatus, ""); err != nil {
|
||||
return nil, fmt.Errorf("record ai session creation: %w", err)
|
||||
}
|
||||
return &session, nil
|
||||
}
|
||||
|
||||
func selectedAuditMetadata(selected SelectedKey) (string, string) {
|
||||
provider := strings.TrimSpace(selected.Provider)
|
||||
keyType := strings.TrimSpace(selected.KeyType)
|
||||
if provider == "" {
|
||||
provider = "none"
|
||||
}
|
||||
if keyType == "" {
|
||||
keyType = "none"
|
||||
}
|
||||
return provider, keyType
|
||||
}
|
||||
|
||||
func aiSessionStatus(session models.SenlinAgentAISession) string {
|
||||
if status := strings.TrimSpace(session.Status); status != "" {
|
||||
return status
|
||||
|
||||
Reference in New Issue
Block a user