sendChatRequest method

  1. @override
Future<Map<String, dynamic>> sendChatRequest(
  1. List<Map<String, dynamic>> messages,
  2. List<Map<String, dynamic>>? tools, {
  3. double? temperature,
  4. int? maxCompletionTokens,
  5. double? topP,
  6. bool? stream,
  7. String? reasoningEffort,
  8. dynamic stop,
  9. CancellationToken? cancellationToken,
})
override

Sends a single, complete chat request to the LLM.

Implementation

@override
Future<Map<String, dynamic>> sendChatRequest(
  List<Map<String, dynamic>> messages,
  List<Map<String, dynamic>>? tools, {
  double? temperature,
  int? maxCompletionTokens,
  double? topP,
  bool? stream,
  String? reasoningEffort,
  dynamic stop,
  CancellationToken? cancellationToken,
}) async {
  sdkLogger.info(
    'Sending chat request to Gemini (model: $model)',
    tag: 'API',
    extra: {
      'model': model,
      'message_count': messages.length,
      'has_tools': tools != null && tools.isNotEmpty,
    },
  );

  final geminiData = _convertToGeminiFormat(messages, tools);

  final generationConfig = {
    'maxOutputTokens': maxCompletionTokens ?? maxTokens,
    if ((temperature ?? this.temperature) != null)
      'temperature': temperature ?? this.temperature,
    if (topP != null) 'topP': topP,
    if (stop != null) 'stopSequences': stop is List ? stop : [stop],
  };

  final bodyMap = {
    if (geminiData['systemInstruction'] != null)
      'systemInstruction': geminiData['systemInstruction'],
    'contents': geminiData['contents'],
    if (geminiData['tools'] != null) 'tools': geminiData['tools'],
    'generationConfig': generationConfig,
  };

  const int maxRetries = 3;
  const Duration baseDelay = Duration(seconds: 1);

  for (int attempt = 1; attempt <= maxRetries; attempt++) {
    if (cancellationToken?.isCancelled == true) {
      throw VanturaCancellationException();
    }

    final url = '$baseUrl/models/$model:generateContent?key=$apiKey';

    try {
      final stopwatch = Stopwatch()..start();
      final response = await _httpClient.post(
        Uri.parse(url),
        headers: _headers,
        body: jsonEncode(bodyMap),
      );
      stopwatch.stop();

      sdkLogger.logPerformance(
        'Gemini API request ($model)',
        stopwatch.elapsed,
        context: {'status_code': response.statusCode},
      );

      if (response.statusCode == 200) {
        return _convertFromGeminiResponse(jsonDecode(response.body));
      } else if (response.statusCode == 429 || response.statusCode >= 500) {
        if (attempt == maxRetries) {
          throw VanturaApiException(
            'Gemini API error after $maxRetries attempts',
            statusCode: response.statusCode,
            responseBody: response.body,
          );
        }

        final retrySeconds = attempt * 2;
        sdkLogger.warning(
          'Gemini API hit ${response.statusCode}. Retrying in ${retrySeconds}s... (Attempt $attempt/$maxRetries)',
          tag: 'API',
        );

        if (onRetry != null) {
          onRetry!(
            attempt,
            Duration(seconds: retrySeconds),
            'HTTP ${response.statusCode}',
          );
        }

        await Future.delayed(Duration(seconds: retrySeconds));
        continue;
      } else {
        throw VanturaApiException(
          'Gemini API error',
          statusCode: response.statusCode,
          responseBody: response.body,
        );
      }
    } on http.ClientException catch (_) {
      if (attempt == maxRetries) rethrow;
      await Future.delayed(baseDelay * attempt);
    }
  }

  throw Exception('Unexpected error in Gemini request');
}