worker/internal/clients/gigachat/client.go

236 lines
5.9 KiB
Go
Raw Normal View History

package gigachat
import (
"context"
"errors"
"fmt"
"gitea.pena/SQuiz/common/model"
"gitea.pena/SQuiz/worker/internal/senders"
"github.com/go-redis/redis/v8"
"github.com/go-resty/resty/v2"
"github.com/google/uuid"
"go.uber.org/zap"
"time"
)
type Deps struct {
Logger *zap.Logger
Client *resty.Client
BaseURL string
AuthKey string
RedisClient *redis.Client
TgSender *senders.TgSender
TgChatID int64
}
type GigaChatClient struct {
logger *zap.Logger
client *resty.Client
baseURL string
authKey string
redisClient *redis.Client
tgSender *senders.TgSender
tgChatID int64
}
func NewGigaChatClient(ctx context.Context, deps Deps) (*GigaChatClient, error) {
client := &GigaChatClient{
logger: deps.Logger,
client: deps.Client,
baseURL: deps.BaseURL,
authKey: deps.AuthKey,
redisClient: deps.RedisClient,
tgSender: deps.TgSender,
tgChatID: deps.TgChatID,
}
if err := client.updateToken(ctx); err != nil {
return nil, fmt.Errorf("failed to get access token: %w", err)
}
return client, nil
}
func (r *GigaChatClient) SendMsg(ctx context.Context, audience model.GigaChatAudience, question model.Question) (string, error) {
2025-06-06 14:27:43 +00:00
gender := ""
switch audience.Sex {
case 0:
gender = "женский"
case 1:
2025-05-10 10:22:36 +00:00
gender = "мужской"
2025-06-06 14:27:43 +00:00
case 2:
gender = "не имеет значения"
2025-05-10 10:22:36 +00:00
}
2025-06-06 14:27:43 +00:00
2025-05-10 10:22:36 +00:00
userInput := fmt.Sprintf(model.ReworkQuestionPrompt, audience.Age, gender, question.Title, question.Description)
token, err := r.redisClient.Get(ctx, "gigachat_token").Result()
if err != nil {
r.logger.Error("failed to get token from redis", zap.Error(err))
return "", err
}
reqBody := model.GigaChatRequest{
Model: "GigaChat-2-Max",
Stream: false,
UpdateInterval: 0,
Messages: []model.GigaChatMessage{
{Role: "system", Content: model.CreatePrompt},
{Role: "user", Content: userInput},
},
}
var response model.GigaChatResponse
resp, err := r.client.R().
SetHeader("Content-Type", "application/json").
SetHeader("Authorization", "Bearer "+token).
SetBody(reqBody).
SetResult(&response).
Post(r.baseURL + "/chat/completions")
if err != nil {
r.logger.Error("failed send request to GigaChat", zap.Error(err))
return "", err
}
if resp.IsError() {
errMsg := fmt.Sprintf("error GigaChat API: %s", resp.Status())
r.logger.Error(errMsg)
return "", errors.New(errMsg)
}
if len(response.Choices) == 0 || response.Choices[0].Message.Content == "" {
// когда возникает такая ошибка то значит еще траим отправить запрос
return "", model.EmptyResponseErrorGigaChat
}
return response.Choices[0].Message.Content, nil
}
func (r *GigaChatClient) TokenResearch(ctx context.Context) {
ticker := time.NewTicker(time.Minute)
defer ticker.Stop()
for {
select {
case <-ticker.C:
ttl, err := r.redisClient.TTL(ctx, "gigachat_token").Result()
fmt.Println("GGCHATtoken", ttl, err, ttl < 2*time.Minute)
if err != nil || ttl < 2*time.Minute {
if err := r.updateToken(ctx); err != nil {
r.logger.Error("failed to update GigaChat token", zap.Error(err))
} else {
r.logger.Info("successfully updated GigaChat token")
}
}
case <-ctx.Done():
return
}
}
}
func (r *GigaChatClient) updateToken(ctx context.Context) error {
2025-05-28 22:13:31 +00:00
formData := "scope=GIGACHAT_API_B2B"
var respData struct {
AccessToken string `json:"access_token"`
ExpiresAt int64 `json:"expires_at"`
}
resp, err := r.client.R().
SetHeader("Authorization", "Basic "+r.authKey).
SetHeader("Content-Type", "application/x-www-form-urlencoded").
SetBody(formData).
SetHeader("RqUID", uuid.New().String()).
SetResult(&respData).
Post("https://ngw.devices.sberbank.ru:9443/api/v2/oauth")
if err != nil {
return err
}
if resp.IsError() {
return fmt.Errorf("token request failed: %s", resp.Status())
}
2025-06-13 11:13:20 +00:00
ttl := time.Until(time.Unix(int64(respData.ExpiresAt/1000), 0))
fmt.Println("GGCTOKENEXP", respData.ExpiresAt, ttl, ttl < 2*time.Minute, time.Now())
err = r.redisClient.Set(ctx, "gigachat_token", respData.AccessToken, ttl).Err()
if err != nil {
return fmt.Errorf("failed to save token to redis: %w", err)
}
return nil
}
func (r *GigaChatClient) getBalance(ctx context.Context) (int, error) {
var respData struct {
Balance []struct {
Usage string `json:"usage"`
Value int `json:"value"`
} `json:"balance"`
}
token, err := r.redisClient.Get(ctx, "gigachat_token").Result()
if err != nil {
r.logger.Error("failed to get token from redis", zap.Error(err))
return 0, err
}
resp, err := r.client.R().
SetHeader("Authorization", "Bearer "+token).
SetResult(&respData).
Get(r.baseURL + "/balance")
if err != nil {
return 0, fmt.Errorf("failed to fetch balance: %w", err)
}
if resp.IsError() {
return 0, fmt.Errorf("balance request failed: %s", resp.Status())
}
// прверяем то что используем для переформулирования
for _, item := range respData.Balance {
if item.Usage == "GigaChat-Max" {
return item.Value, nil
}
}
return 0, errors.New("no used models found")
}
func (r *GigaChatClient) MonitorTokenBalance(ctx context.Context) {
ticker := time.NewTicker(5 * time.Minute)
defer ticker.Stop()
alert := false // чтоб не спамить каждые 5 минут
for {
select {
case <-ticker.C:
balance, err := r.getBalance(ctx)
if err != nil {
r.logger.Error("failed to get GigaChat token", zap.Error(err))
continue
}
if balance < 500_000 && !alert {
msg := fmt.Sprintf("Остаток токенов в GigaChat упал ниже 500000.\nТекущий баланс: %d токенов.", balance)
if err := r.tgSender.SendMessage(r.tgChatID, msg); err != nil {
r.logger.Error("failed to send Telegram alert", zap.Error(err))
} else {
alert = true
}
}
if balance >= 500_000 && alert {
alert = false
}
case <-ctx.Done():
return
}
}
}