Skip to content

Commit 4716f4e

Browse files
overload method of UploadMedia to support stream types #38
1 parent 105a55d commit 4716f4e

File tree

2 files changed

+86
-1
lines changed

2 files changed

+86
-1
lines changed

src/Mscc.GenerativeAI/GenerativeModel.cs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@ public async Task<string> TransferOwnership(string model, string emailAddress)
499499
/// Uploads a file to the File API backend.
500500
/// </summary>
501501
/// <param name="uri">URI or path to the file to upload.</param>
502-
/// <param name="displayName">A name displazed for the uploaded file.</param>
502+
/// <param name="displayName">A name displayed for the uploaded file.</param>
503503
/// <param name="resumable">Flag indicating whether to use resumable upload.</param>
504504
/// <param name="cancellationToken">A cancellation token to cancel the upload.</param>
505505
/// <returns>A URI of the uploaded file.</returns>
@@ -557,6 +557,53 @@ public async Task<UploadMediaResponse> UploadFile(string uri,
557557
}
558558
}
559559

560+
public async Task<UploadMediaResponse> UploadFile(Stream stream,
561+
string displayName,
562+
string mimeType,
563+
bool resumable = false,
564+
CancellationToken cancellationToken = default)
565+
{
566+
if (stream == null) throw new ArgumentNullException(nameof(stream));
567+
if (stream.Length > Constants.MaxUploadFileSize) throw new MaxUploadFileSizeException(nameof(stream));
568+
if (string.IsNullOrEmpty(mimeType)) throw new ArgumentException(nameof(mimeType));
569+
if (string.IsNullOrEmpty(displayName)) throw new ArgumentException(nameof(displayName));
570+
571+
var totalBytes = stream.Length;
572+
var request = new UploadMediaRequest()
573+
{
574+
File = new FileRequest()
575+
{
576+
DisplayName = displayName
577+
}
578+
};
579+
580+
var url = $"{EndpointGoogleAi}/upload/{Version}/files"; // v1beta3 // ?key={apiKey}
581+
if (resumable)
582+
{
583+
url = $"{EndpointGoogleAi}/resumable/upload/{Version}/files"; // v1beta3 // ?key={apiKey}
584+
}
585+
url = ParseUrl(url).AddQueryString(new Dictionary<string, string?>()
586+
{
587+
["alt"] = "json",
588+
["uploadType"] = "multipart"
589+
});
590+
string json = Serialize(request);
591+
592+
var multipartContent = new MultipartContent("related");
593+
multipartContent.Add(new StringContent(json, Encoding.UTF8, Constants.MediaType));
594+
multipartContent.Add(new StreamContent(stream, (int)Constants.ChunkSize)
595+
{
596+
Headers = {
597+
ContentType = new MediaTypeHeaderValue(mimeType),
598+
ContentLength = totalBytes
599+
}
600+
});
601+
602+
var response = await Client.PostAsync(url, multipartContent, cancellationToken);
603+
await response.EnsureSuccessAsync();
604+
return await Deserialize<UploadMediaResponse>(response);
605+
}
606+
560607
/// <summary>
561608
/// Lists the metadata for Files owned by the requesting project.
562609
/// </summary>

tests/Mscc.GenerativeAI/GoogleAi_Gemini15Pro_Should.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,44 @@ public async void Upload_File_WithResume_Using_FileAPI()
349349
File.Delete(filePath);
350350
}
351351

352+
[Theory]
353+
[InlineData("scones.jpg", "Set of blueberry scones", "image/jpeg")]
354+
[InlineData("cat.jpg", "Wildcat on snow", "image/jpeg")]
355+
[InlineData("cat.jpg", "Cat in the snow", "image/jpeg")]
356+
[InlineData("image.jpg", "Sample drawing", "image/jpeg")]
357+
[InlineData("animals.mp4", "Zootopia in da house", "video/mp4")]
358+
[InlineData("sample.mp3", "State_of_the_Union_Address_30_January_1961", "audio/mp3")]
359+
[InlineData("pixel.mp3", "Pixel Feature Drops: March 2023", "audio/mp3")]
360+
[InlineData("gemini.pdf",
361+
"Gemini 1.5: Unlocking multimodal understanding across millions of tokens of context", "application/pdf")]
362+
public async void Upload_Stream_Using_FileAPI(string filename, string displayName, string mimeType)
363+
{
364+
// Arrange
365+
var filePath = Path.Combine(Environment.CurrentDirectory, "payload", filename);
366+
IGenerativeAI genAi = new GoogleAI(_fixture.ApiKey);
367+
var model = genAi.GenerativeModel(_model);
368+
369+
// Act
370+
using (var fs = new FileStream(filePath, FileMode.Open))
371+
{
372+
var response = await model.UploadFile(fs, displayName, mimeType);
373+
374+
// Assert
375+
response.Should().NotBeNull();
376+
response.File.Should().NotBeNull();
377+
response.File.Name.Should().NotBeNull();
378+
response.File.DisplayName.Should().Be(displayName);
379+
// response.File.MimeType.Should().Be("image/jpeg");
380+
// response.File.CreateTime.Should().BeGreaterThan(DateTime.Now.Add(TimeSpan.FromHours(48)));
381+
// response.File.ExpirationTime.Should().NotBeNull();
382+
// response.File.UpdateTime.Should().NotBeNull();
383+
response.File.SizeBytes.Should().BeGreaterThan(0);
384+
response.File.Sha256Hash.Should().NotBeNull();
385+
response.File.Uri.Should().NotBeNull();
386+
_output.WriteLine($"Uploaded file '{response?.File.DisplayName}' as: {response?.File.Uri}");
387+
}
388+
}
389+
352390
[Fact]
353391
public async void List_Files()
354392
{

0 commit comments

Comments
 (0)