daily_digest/Services/GoogleGeminiSummarizationService.cs
2025-04-12 01:46:09 +07:00

164 lines
9.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using DailyDigestWorker.Configuration;
using DailyDigestWorker.Models.Gemini; // Наши DTO
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using System.Text; // Для StringBuilder и Encoding
namespace DailyDigestWorker.Services
{
public class GoogleGeminiSummarizationService : ISummarizationService
{
private readonly ILogger<GoogleGeminiSummarizationService> _logger;
private readonly IHttpClientFactory _httpClientFactory;
private readonly ApiKeys _apiKeys;
private readonly GeminiSettings _geminiSettings;
private readonly string _apiKey;
// Внедряем зависимости
public GoogleGeminiSummarizationService(
ILogger<GoogleGeminiSummarizationService> logger,
IHttpClientFactory httpClientFactory,
IOptions<ApiKeys> apiKeysOptions,
IOptions<GeminiSettings> geminiSettingsOptions)
{
_logger = logger;
_httpClientFactory = httpClientFactory;
_apiKeys = apiKeysOptions.Value;
_geminiSettings = geminiSettingsOptions.Value;
_apiKey = _apiKeys.Gemini; // Сохраняем ключ для удобства
}
public async Task<string?> SummarizeTextAsync(string textToSummarize, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(_apiKey))
{
_logger.LogError("Ключ API для Google Gemini не сконфигурирован.");
return null;
}
if (string.IsNullOrWhiteSpace(textToSummarize))
{
_logger.LogWarning("Получен пустой текст для суммаризации.");
return null; // Нечего суммаризировать
}
// Формируем URL
string requestUrl = string.Format(_geminiSettings.ApiEndpointFormat, _geminiSettings.ModelName, _apiKey);
_logger.LogInformation("Запрос саммари к Google Gemini API (Модель: {Model})...", _geminiSettings.ModelName);
// Формируем УЛУЧШЕННЫЙ промпт
var prompt = new StringBuilder();
prompt.AppendLine("Ты - редактор, готовящий краткую новостную сводку по городу Новокузнецку для Telegram-дайджеста.")
.AppendLine("Проанализируй следующие тексты новостей, появившихся за последние 24 часа:")
.AppendLine("--- ТЕКСТЫ НОВОСТЕЙ НАЧАЛО ---")
.AppendLine(textToSummarize)
.AppendLine("--- ТЕКСТЫ НОВОСТЕЙ КОНЕЦ ---")
.AppendLine("\nТвоя задача:")
.AppendLine("1. Выделить 10 САМЫХ ВАЖНЫХ и интересных событий.")
.AppendLine("2. Для каждого события сформулировать ОЧЕНЬ КРАТКОЕ описание (1-2 предложения максимум).")
.AppendLine("3. Представить результат в виде НЕНУМЕРОВАННОГО списка, используя эмоджи в качестве маркера для каждого пункта.")
.AppendLine("4. Использовать нейтральный и информативный тон. Без эмоций, оценок, приветствий и заключений.")
.AppendLine("5. Не добавляй заголовок к списку новостей, только сам список.")
.AppendLine("\nПример желаемого формата вывода:")
.AppendLine("- Краткое описание первого события.")
.AppendLine("- Краткое описание второго события.")
.AppendLine("\nСгенерируй сводку:")
;
// Создаем тело запроса с помощью DTO
var requestDto = new GeminiRequestDto
{
Contents = new List<ContentDto>
{
new ContentDto { Parts = new List<PartDto> { new PartDto { Text = prompt.ToString() } } }
},
GenerationConfig = new GenerationConfigDto
{
Temperature = _geminiSettings.Temperature,
MaxOutputTokens = _geminiSettings.MaxOutputTokens
}
// Можно добавить SafetySettings, если нужно
};
// Сериализуем DTO в JSON
string jsonPayload = JsonConvert.SerializeObject(requestDto);
_logger.LogDebug("Тело запроса Gemini JSON: {Payload}", jsonPayload); // Осторожно, может быть большим
var client = _httpClientFactory.CreateClient();
try
{
// Создаем и отправляем POST-запрос
using var request = new HttpRequestMessage(HttpMethod.Post, requestUrl);
request.Content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
HttpResponseMessage response = await client.SendAsync(request, cancellationToken);
string responseBody = await response.Content.ReadAsStringAsync(cancellationToken);
if (!response.IsSuccessStatusCode)
{
_logger.LogError("Ошибка при запросе к Gemini API. Статус: {StatusCode}. Ответ: {ResponseBody}", response.StatusCode, responseBody);
return null;
}
_logger.LogDebug("Ответ от Gemini API: {ResponseBody}", responseBody);
// Десериализуем ответ
var responseDto = JsonConvert.DeserializeObject<GeminiResponseDto>(responseBody);
// Проверяем наличие кандидатов и текста
var candidate = responseDto?.Candidates?.FirstOrDefault();
var generatedText = candidate?.Content?.Parts?.FirstOrDefault()?.Text;
// Проверяем причину завершения (на случай блокировки по безопасности)
if (candidate?.FinishReason != null && candidate.FinishReason != "STOP")
{
_logger.LogWarning("Генерация Gemini завершилась с причиной: {FinishReason}", candidate.FinishReason);
// Дополнительно логируем safety ratings, если они есть
if (candidate.SafetyRatings != null && candidate.SafetyRatings.Any(sr => sr.Blocked ?? false))
{
_logger.LogWarning("Генерация заблокирована по безопасности: {@SafetyRatings}", candidate.SafetyRatings);
}
if (responseDto?.PromptFeedback?.BlockReason != null)
{
_logger.LogWarning("Промпт заблокирован по безопасности: {BlockReason}", responseDto.PromptFeedback.BlockReason);
}
// В зависимости от причины, можно вернуть null или специальное сообщение
// return $"Не удалось сгенерировать саммари (причина: {candidate.FinishReason})";
return null; // Возвращаем null, если генерация не завершилась нормально
}
if (string.IsNullOrWhiteSpace(generatedText))
{
_logger.LogWarning("Gemini API вернул пустой результат или не удалось извлечь текст.");
return null;
}
_logger.LogInformation("Саммари успешно получено от Gemini API.");
return generatedText.Trim(); // Возвращаем полученный текст, убрав лишние пробелы
}
catch (HttpRequestException ex)
{
_logger.LogError(ex, "Ошибка HTTP при запросе к Gemini API.");
return null;
}
catch (JsonException ex)
{
_logger.LogError(ex, "Ошибка парсинга JSON ответа Gemini API.");
return null;
}
catch (OperationCanceledException)
{
_logger.LogInformation("Запрос саммари был отменен.");
return null;
}
catch (Exception ex)
{
_logger.LogError(ex, "Неожиданная ошибка при получении саммари от Gemini API.");
return null;
}
}
}
}