All checks were successful
Deploy Smart Search Backend Test / deploy (push) Successful in 1m44s
208 lines
5.4 KiB
Go
208 lines
5.4 KiB
Go
package service
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"math"
|
||
|
||
"git.techease.ru/Smart-search/smart-search-back/internal/ai"
|
||
"git.techease.ru/Smart-search/smart-search-back/internal/model"
|
||
"git.techease.ru/Smart-search/smart-search-back/internal/repository"
|
||
"git.techease.ru/Smart-search/smart-search-back/pkg/errors"
|
||
"git.techease.ru/Smart-search/smart-search-back/pkg/fileparser"
|
||
"github.com/google/uuid"
|
||
"github.com/jackc/pgx/v5"
|
||
)
|
||
|
||
type requestService struct {
|
||
requestRepo repository.RequestRepository
|
||
supplierRepo repository.SupplierRepository
|
||
tokenUsageRepo repository.TokenUsageRepository
|
||
userRepo repository.UserRepository
|
||
openAI *ai.OpenAIClient
|
||
perplexity *ai.PerplexityClient
|
||
txManager *repository.TxManager
|
||
}
|
||
|
||
func NewRequestService(
|
||
requestRepo repository.RequestRepository,
|
||
supplierRepo repository.SupplierRepository,
|
||
tokenUsageRepo repository.TokenUsageRepository,
|
||
userRepo repository.UserRepository,
|
||
openAI *ai.OpenAIClient,
|
||
perplexity *ai.PerplexityClient,
|
||
txManager *repository.TxManager,
|
||
) RequestService {
|
||
return &requestService{
|
||
requestRepo: requestRepo,
|
||
supplierRepo: supplierRepo,
|
||
tokenUsageRepo: tokenUsageRepo,
|
||
userRepo: userRepo,
|
||
openAI: openAI,
|
||
perplexity: perplexity,
|
||
txManager: txManager,
|
||
}
|
||
}
|
||
|
||
func (s *requestService) CreateTZ(ctx context.Context, userID int, requestTxt string, fileData []byte, fileName string) (uuid.UUID, string, error) {
|
||
combinedText := requestTxt
|
||
|
||
if len(fileData) > 0 && fileName != "" {
|
||
fileContent, err := fileparser.ExtractText(fileData, fileName)
|
||
if err != nil {
|
||
return uuid.Nil, "", err
|
||
}
|
||
if fileContent != "" {
|
||
if combinedText != "" {
|
||
combinedText = fmt.Sprintf("%s\n\nСодержимое файла (%s):\n%s", combinedText, fileName, fileContent)
|
||
} else {
|
||
combinedText = fmt.Sprintf("Содержимое файла (%s):\n%s", fileName, fileContent)
|
||
}
|
||
}
|
||
}
|
||
|
||
req := &model.Request{
|
||
UserID: userID,
|
||
RequestTxt: combinedText,
|
||
}
|
||
|
||
if err := s.requestRepo.Create(ctx, req); err != nil {
|
||
return uuid.Nil, "", err
|
||
}
|
||
|
||
if combinedText == "" {
|
||
return req.ID, "", nil
|
||
}
|
||
|
||
tzText, err := s.openAI.GenerateTZ(combinedText)
|
||
if err != nil {
|
||
if err := s.requestRepo.UpdateWithTZ(ctx, req.ID, "", false); err != nil {
|
||
return req.ID, "", err
|
||
}
|
||
return req.ID, "", err
|
||
}
|
||
|
||
inputLen := len(requestTxt)
|
||
outputLen := len(tzText)
|
||
promptTokens := 500
|
||
|
||
inputTokens := int(math.Ceil(float64(inputLen) / 2.0))
|
||
outputTokens := int(math.Ceil(float64(outputLen) / 2.0))
|
||
|
||
totalTokens := inputTokens + outputTokens + promptTokens
|
||
tokenPrice := 25000.0 / 1000000.0
|
||
cost := float64(totalTokens) * tokenPrice
|
||
|
||
tokenUsage := &model.TokenUsage{
|
||
RequestID: req.ID,
|
||
RequestTokenCount: inputTokens + promptTokens,
|
||
ResponseTokenCount: outputTokens,
|
||
TokenCost: cost,
|
||
Type: "tz",
|
||
}
|
||
|
||
err = s.txManager.WithTx(ctx, func(tx pgx.Tx) error {
|
||
if err := s.tokenUsageRepo.CreateTx(ctx, tx, tokenUsage); err != nil {
|
||
return err
|
||
}
|
||
|
||
if err := s.userRepo.UpdateBalanceTx(ctx, tx, userID, -cost); err != nil {
|
||
return err
|
||
}
|
||
|
||
if err := s.requestRepo.UpdateWithTZTx(ctx, tx, req.ID, tzText, true); err != nil {
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
})
|
||
|
||
if err != nil {
|
||
return req.ID, "", err
|
||
}
|
||
|
||
return req.ID, tzText, nil
|
||
}
|
||
|
||
func (s *requestService) ApproveTZ(ctx context.Context, requestID uuid.UUID, tzText string, userID int) ([]*model.Supplier, error) {
|
||
isOwner, err := s.requestRepo.CheckOwnership(ctx, requestID, userID)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if !isOwner {
|
||
return nil, errors.NewBusinessError(errors.PermissionDenied, "access denied to this request")
|
||
}
|
||
|
||
if err = s.requestRepo.UpdateFinalTZ(ctx, requestID, tzText); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
var suppliers []*model.Supplier
|
||
var promptTokens, responseTokens int
|
||
|
||
for attempt := 0; attempt < 3; attempt++ {
|
||
suppliers, promptTokens, responseTokens, err = s.perplexity.FindSuppliers(tzText)
|
||
if err == nil {
|
||
break
|
||
}
|
||
}
|
||
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if len(suppliers) == 0 {
|
||
return nil, errors.NewInternalError(errors.AIAPIError, "no suppliers found", nil)
|
||
}
|
||
|
||
tokenPrice := 25000.0 / 1000000.0
|
||
totalTokens := promptTokens + responseTokens
|
||
cost := float64(totalTokens) * tokenPrice
|
||
|
||
tokenUsage := &model.TokenUsage{
|
||
RequestID: requestID,
|
||
RequestTokenCount: promptTokens,
|
||
ResponseTokenCount: responseTokens,
|
||
TokenCost: cost,
|
||
Type: "suppliers",
|
||
}
|
||
|
||
err = s.txManager.WithTx(ctx, func(tx pgx.Tx) error {
|
||
if err := s.supplierRepo.BulkInsertTx(ctx, tx, requestID, suppliers); err != nil {
|
||
return err
|
||
}
|
||
|
||
if err := s.tokenUsageRepo.CreateTx(ctx, tx, tokenUsage); err != nil {
|
||
return err
|
||
}
|
||
|
||
if err := s.userRepo.UpdateBalanceTx(ctx, tx, userID, -cost); err != nil {
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
})
|
||
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return suppliers, nil
|
||
}
|
||
|
||
func (s *requestService) GetMailingList(ctx context.Context, userID int) ([]*model.Request, error) {
|
||
return s.requestRepo.GetByUserID(ctx, userID)
|
||
}
|
||
|
||
func (s *requestService) GetMailingListByID(ctx context.Context, requestID uuid.UUID, userID int) (*model.RequestDetail, error) {
|
||
isOwner, err := s.requestRepo.CheckOwnership(ctx, requestID, userID)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if !isOwner {
|
||
return nil, errors.NewBusinessError(errors.PermissionDenied, "access denied to this request")
|
||
}
|
||
|
||
return s.requestRepo.GetDetailByID(ctx, requestID)
|
||
}
|