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) { gender := "" switch audience.Sex { case 0: gender = "женский" case 1: gender = "мужской" case 2: gender = "не имеет значения" } 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 { 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()) } 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 } } }