feat: add ai gateway foundation

This commit is contained in:
2026-07-18 16:05:02 +08:00
parent b7fb1969e6
commit 833d9f094f
3 changed files with 150 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
package ai
import (
"errors"
"gorm.io/gorm"
"senlinai-agent/backend/internal/domain"
)
type Gateway struct {
db *gorm.DB
systemKey string
}
type SelectedKey struct {
Provider string
APIKey string
KeyType string
}
func NewGateway(database *gorm.DB, systemKey string) *Gateway {
return &Gateway{db: database, systemKey: systemKey}
}
func (g *Gateway) SelectKey(userID uint) (SelectedKey, error) {
var userKey domain.AIKey
if err := g.db.Where("user_id = ?", userID).First(&userKey).Error; err == nil {
return SelectedKey{Provider: userKey.Provider, APIKey: userKey.EncryptedAPIKey, KeyType: "user"}, nil
}
if g.systemKey == "" {
return SelectedKey{}, errors.New("no ai key available")
}
return SelectedKey{Provider: "openai", APIKey: g.systemKey, KeyType: "system"}, nil
}
func (g *Gateway) RecordCall(userID uint, provider string, usedKeyType string, action string, status string, errText string) error {
return g.db.Create(&domain.AICallLog{
UserID: userID,
Provider: provider,
UsedKeyType: usedKeyType,
Action: action,
Status: status,
Error: errText,
}).Error
}

View File

@@ -0,0 +1,79 @@
package ai
import (
"fmt"
"testing"
"github.com/glebarez/sqlite"
"github.com/stretchr/testify/require"
"gorm.io/gorm"
"senlinai-agent/backend/internal/domain"
)
func TestSelectKeyPrefersUserKey(t *testing.T) {
database := newTestDB(t)
require.NoError(t, database.Create(&domain.AIKey{UserID: 3, Provider: "openai", EncryptedAPIKey: "user-key"}).Error)
gateway := NewGateway(database, "system-key")
selected, err := gateway.SelectKey(3)
require.NoError(t, err)
require.Equal(t, "user", selected.KeyType)
require.Equal(t, "user-key", selected.APIKey)
}
func TestSelectKeyFallsBackToSystemKey(t *testing.T) {
database := newTestDB(t)
gateway := NewGateway(database, "system-key")
selected, err := gateway.SelectKey(3)
require.NoError(t, err)
require.Equal(t, "system", selected.KeyType)
require.Equal(t, "system-key", selected.APIKey)
}
func TestSelectKeyReturnsErrorWhenNoKeyAvailable(t *testing.T) {
database := newTestDB(t)
gateway := NewGateway(database, "")
_, err := gateway.SelectKey(3)
require.ErrorContains(t, err, "no ai key available")
}
func TestRecordCallStoresAuditFields(t *testing.T) {
database := newTestDB(t)
gateway := NewGateway(database, "system-key")
require.NoError(t, gateway.RecordCall(3, "openai", "system", "inbox_analyze", "failed", "rate limited"))
var log domain.AICallLog
require.NoError(t, database.First(&log).Error)
require.Equal(t, uint(3), log.UserID)
require.Equal(t, "openai", log.Provider)
require.Equal(t, "system", log.UsedKeyType)
require.Equal(t, "inbox_analyze", log.Action)
require.Equal(t, "failed", log.Status)
require.Equal(t, "rate limited", log.Error)
}
func TestCreateAISession(t *testing.T) {
database := newTestDB(t)
service := NewSessionService(database)
session, err := service.Create(7, 3, "报价分析")
require.NoError(t, err)
require.Equal(t, uint(7), session.ProjectID)
require.Equal(t, uint(3), session.CreatedBy)
require.Equal(t, "报价分析", session.Title)
}
func newTestDB(t *testing.T) *gorm.DB {
t.Helper()
database, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
require.NoError(t, err)
require.NoError(t, domain.AutoMigrate(database))
return database
}

View File

@@ -0,0 +1,26 @@
package ai
import (
"errors"
"strings"
"gorm.io/gorm"
"senlinai-agent/backend/internal/domain"
)
type SessionService struct {
db *gorm.DB
}
func NewSessionService(database *gorm.DB) *SessionService {
return &SessionService{db: database}
}
func (s *SessionService) Create(projectID uint, userID uint, title string) (*domain.AISession, error) {
title = strings.TrimSpace(title)
if title == "" {
return nil, errors.New("session title is required")
}
session := &domain.AISession{ProjectID: projectID, CreatedBy: userID, Title: title}
return session, s.db.Create(session).Error
}