using AutoGen.Core; using Mscc.GenerativeAI; using System.Diagnostics; using static AiAgent.Core.Constant.ProgramConstants; using static AiAgent.Core.Helpers.GenericHelper; using System.Text.Json; namespace AiAgent.Core.Infrastructure; /// /// A client for interacting with the Gemini API, providing methods for querying generative models and generating images. /// /// This class encapsulates functionality to interact with Google's Gemini API, including methods to query /// generative models with text prompts and input files, generate images based on text prompts, and log usage /// metadata and errors. It supports multiple API keys and model configurations to optimize for cost and performance. public class GeminiApiClient { private static string[] PaidApiKeys => ["AIzaSyCFVgyBJtrEIe5S7mvttxEzGRYILUzGNhg"]; // Pay private static string[] FreeApiKeys => ["AIzaSyAsphWA1WPfht3DbL2BBTa5bSknyIU1oHA", "AIzaSyBr7Hz70vQCnFYA7ij7_vmkLCbi2d6B1Pw"]; // Free private static string[] ImageModels => [ //"imagen-4.0-ultra-generate-001", //"imagen-4.0-generate-001", "imagen-4.0-fast-generate-001", //"imagen-3.0-generate-002" ]; public static async Task Query(string prompt, string tag, GenerativeModel model, GenerationConfig generationConfig, string apiKey, List? inputDataFiles = null) { for (int keyIndex = 0; keyIndex < 2; keyIndex++) { //try //{ // Make sure all the files exist foreach (var file in inputDataFiles ?? []) if (File.Exists(file) is false) return $"The input file '{file}' is missing"; // Create GoogleAI instance to use for file upload var googleAI = new GoogleAI(apiKey); // Create a collection of upload tasks var uploadTasks = inputDataFiles?.Select(filePath => googleAI.UploadFile(filePath, Path.GetFileName(filePath))) ?? throw new Exception("Problem uploading the files"); var results = await Task.WhenAll(uploadTasks); var uploadedFiles = results.Select(response => response.File).ToList(); foreach (var file in uploadedFiles) Log($"- Includes: {file.DisplayName} ({file.MimeType}) {file.SizeBytes} bytes, {file.State} -> {file.Uri}' "); // Initialising the prompt var myPrompt = new List { new TextData { Text = prompt } }; // Add a reference to each uploaded file to the prompt. foreach (var file in uploadedFiles) myPrompt.Add(new FileData { MimeType = file.MimeType, FileUri = file.Uri }); // Use GenerateContentStreamAsync to get the response as a stream. Log("- Starting the query...\n"); var fullThinking = new System.Text.StringBuilder(); var fullResponse = new System.Text.StringBuilder(); Stopwatch stopwatch = Stopwatch.StartNew(); model.ApiKey = apiKey; var stream = model.GenerateContentStream(myPrompt, generationConfig); UsageMetadata? usage = null; Candidate? firstCandidate = null; // Use "await foreach" to process each chunk as it arrives. await foreach (var res in stream) { // Log the chunk to a file string jsonChunk = JsonSerializer.Serialize(res); string message = $"------------------\n{DateTime.Now} -> {stopwatch}\n"; //message += string.IsNullOrEmpty(res.Thinking) ? "" : $"Thinking:\n{res.Thinking}\n"; //message += string.IsNullOrEmpty(res.Text) ? "" : $"Text:\n{res.Text}\n"; File.AppendAllText(Path.Combine(Output, $"Chunks - {tag}.txt"), $"{message}{jsonChunk}\n"); if (res is null) break; if (res.Candidates?.Count > 0) { firstCandidate = res.Candidates[0]; File.AppendAllText(Path.Combine(Output, $"Candidates - {tag} - Index = {keyIndex}.txt"), $"{res.Candidates[0].FinishReason}\n{res.Candidates[0].FinishMessage}\n"); if (res.Candidates[0].FinishReason is not null) if (res.Candidates[0].FinishReason.HasValue) { var candidateJson = JsonSerializer.Serialize(res.Candidates[0]); File.AppendAllText(Path.Combine(Output, $"Candidates - {tag} - Index = {keyIndex}.txt"), $"{candidateJson}\n"); } } try { if (res.Thinking is null && res.Text is null) break; } catch { break; } // First display the thinking if (res.Thinking is not null) { fullThinking.Append(res.Thinking); File.AppendAllText(Path.Combine(Output, $"Thinking - {tag}.txt"), fullThinking.ToString()); LogNoLine(res.Thinking); } // Get the text from the current chunk var chunkText = res.Text; usage = res.UsageMetadata; // THIS is how you see the "thinking" in real-time - handle null case if (chunkText != null) LogNoLine(chunkText); // Append the chunk to our StringBuilder to build the final result fullResponse.Append(chunkText); } // Save the full response to a file File.WriteAllText(Path.Combine(Output, $"{tag} - Full Response.txt"), $"Thinking:\n{fullThinking}\n\nResponse:\n{fullResponse}"); // Write the usage log string header = "DateTime,Elapsed,Tag,Model,PromptTokens,ThoughtsTokens,ToolUsePromptTokens,CandidatesTokens,TotalTokens"; var usageMessage = $"{DateTime.Now},{stopwatch.Elapsed},{tag},{model.Model},{usage?.PromptTokenCount},{usage?.ThoughtsTokenCount},{usage?.ToolUsePromptTokenCount},{usage?.CandidatesTokenCount},{usage?.TotalTokenCount}"; LogScv(Output, "gemini_usage.csv", header, usageMessage); LogScv(Archive, "gemini_usage.csv", header, usageMessage); // After the loop, the StringBuilder contains the complete text. return fullResponse.ToString(); //} //catch (Exception ex) //{ // await LogError(ex); //} } return "Failed: Please check the Error Logs"; } /// /// Logs a line of text to a specified file, creating the file with a header if it does not already exist. /// /// If the specified file does not exist, it will be created, and the provided header will be /// written as the first line. Subsequent calls will append the specified line to the file. /// The directory path where the file is located or will be created. /// The name of the file to which the log entry will be written. /// The header text to write to the file if it is created. /// The line of text to append to the file. public static void LogScv(string path, string file, string header, string line) { string fullPath = Path.Combine(path, file); if (File.Exists(fullPath) is false) File.WriteAllText(fullPath, header + Environment.NewLine); File.AppendAllText(fullPath, line + Environment.NewLine); } /// /// Generates an image based on the specified prompt and saves it to the specified file path. /// /// This method attempts to generate an image using a sequence of models, starting with the most /// advanced. If an error occurs during the process, it retries with progressively less expensive models up to four /// times. The generated image is saved as a file in the specified location. /// The text prompt describing the desired image content. /// The file path where the generated image will be saved. The path must be writable. /// A representing the asynchronous operation. The task result is a string indicating /// the outcome: if the image was generated and saved successfully; otherwise, . public static async Task GenerateImage(string prompt, string outputFile) { var keyUsed = PaidApiKeys; // Cycle through 4 different models until one works for (int modelIndex = 0; modelIndex < ImageModels.Length; modelIndex++) for (int keyIndex = 0; keyIndex < keyUsed.Length; keyIndex++) try { // Use cheaper and cheaper models string imageModel = ImageModels[modelIndex]; string apiKey = keyUsed[keyIndex]; Console.WriteLine($"Generating image with model '{imageModel}'"); // Define the model var googleAI = new GoogleAI(apiKey); var model = googleAI.ImageGenerationModel(imageModel); var requestOptions = new RequestOptions(null, TimeSpan.FromMinutes(2)); // Call the API var response = await model.GenerateImages(prompt, 1, null, ImageAspectRatio.Ratio1x1, null, null, null, null, null, null, requestOptions); // Check for failure, maybe censured if (response.Predictions is null) continue; // Save the image to a file var image = response.Predictions.First(); byte[] imageBytes = Convert.FromBase64String(image.BytesBase64Encoded); await File.WriteAllBytesAsync(outputFile, imageBytes); // We can get out now return "Success"; } catch (Exception ex) { await LogError(ex); } return "Failed"; } /// /// Executes a generative AI query using the specified model, prompt, and input data files, and returns the /// generated response as a string. /// /// This method performs the following steps: Uploads the specified /// input data files to the generative AI model. Constructs a query prompt that includes the provided /// text prompt and references to the uploaded files. Streams the generated response in real-time, /// appending each chunk to build the final result. Logs usage metadata, including token counts and /// elapsed time, to a CSV file. The method attempts to use multiple API keys in sequence if the /// first attempt fails. If all attempts fail, the method returns a failure message. /// The text prompt to guide the generative AI model in producing a response. /// A user-defined tag to categorize or identify the query in logs. /// The name of the generative AI model to use for the query. This should include the full model identifier. /// A list of file paths representing input data files to be uploaded and referenced in the query. /// A representing the asynchronous operation. The task result contains the generated /// response as a string. If the operation fails, the result will be "Failed: Please check the Error Logs". public static async Task Query(string prompt, string tag, string modelName, List inputDataFiles) { for (int keyIndex = 0; keyIndex < 2; keyIndex++) try { // Use cheaper and cheaper models string apiKey = FreeApiKeys[keyIndex]; // Get the model core name string modelCoreName = modelName.Split('/').Last(); Console.WriteLine($"\nStarting Gemini Query with model: {modelCoreName}"); // Define the model var model = new GenerativeModel() { Model = modelCoreName, ApiKey = apiKey, UseGoogleSearch = true, Timeout = TimeSpan.FromMinutes(5) }; // Generation config var generationConfig = new GenerationConfig { Temperature = 0.0f, CandidateCount = 1, ThinkingConfig = new ThinkingConfig { ThinkingBudget = 8000, IncludeThoughts = true, }, }; // Create a collection of upload tasks var uploadTasks = inputDataFiles.Select(filePath => model.UploadFile(filePath, Path.GetFileName(filePath))); // Await all tasks to complete var results = await Task.WhenAll(uploadTasks); var uploadedFiles = results.Select(response => response.File).ToList(); foreach (var file in uploadedFiles) Console.WriteLine($"- Includes: {file.DisplayName} ({file.MimeType}) {file.SizeBytes} bytes, {file.State} -> {file.Uri}' "); // Initialising the prompt var myPrompt = new List { new TextData { Text = prompt } }; // Add a reference to each uploaded file to the prompt. foreach (var file in uploadedFiles) myPrompt.Add(new FileData { MimeType = file.MimeType, FileUri = file.Uri }); // Prepare the log var usageLog = Path.Combine(Archive, "gemini_usage.csv"); if (File.Exists(usageLog) is false) File.WriteAllText(usageLog, "DateTime,Elapsed,Tag,Model,PromptTokens,ThoughtsTokens,ToolUsePromptTokens,CandidatesTokens,TotalTokens\n"); // Use GenerateContentStreamAsync to get the response as a stream. var fullResponse = new System.Text.StringBuilder(); Stopwatch stopwatch = Stopwatch.StartNew(); var stream = model.GenerateContentStream(myPrompt, generationConfig); UsageMetadata? usage = null; // Use "await foreach" to process each chunk as it arrives. await foreach (var res in stream) { // Get the text from the current chunk var chunkText = res.Text; usage = res.UsageMetadata; // THIS is how you see the "thinking" in real-time Console.Write(chunkText); // Append the chunk to our StringBuilder to build the final result fullResponse.Append(chunkText); } File.AppendAllText(usageLog, $"{DateTime.Now},{stopwatch.Elapsed},{tag},{model.Model}," + $"{usage?.PromptTokenCount},{usage?.ThoughtsTokenCount},{usage?.ToolUsePromptTokenCount},{usage?.CandidatesTokenCount},{usage?.TotalTokenCount}\n"); // After the loop, the StringBuilder contains the complete text. return fullResponse.ToString(); } catch (Exception ex) { await LogError(ex); } return "Failed: Please check the Error Logs"; } public static async Task NanoBanana(string prompt, string tag) { try { // Define the model var model = new GenerativeModel() { Model = Model.Gemini25FlashImage, ApiKey = PaidApiKeys.First(), UseGoogleSearch = false, Timeout = TimeSpan.FromMinutes(5) }; // Generation config var generationConfig = new GenerationConfig { Temperature = 0.0f, CandidateCount = 1, ResponseModalities = [ResponseModality.Image] }; Console.WriteLine($"\nStarting Gemini Image Query with model: {model.Model}"); // Initialising the prompt var myPrompt = new List { new TextData { Text = prompt } }; // Prepare the log var usageLog = Path.Combine(Archive, "gemini_usage.csv"); if (File.Exists(usageLog) is false) File.WriteAllText(usageLog, "DateTime,Elapsed,Tag,Model,PromptTokens,ThoughtsTokens,ToolUsePromptTokens,CandidatesTokens,TotalTokens\n"); // Use GenerateContentStreamAsync to get the response as a stream. var fullResponse = new System.Text.StringBuilder(); Stopwatch stopwatch = Stopwatch.StartNew(); var stream = model.GenerateContentStream(myPrompt, generationConfig); UsageMetadata? usage = null; // Use "await foreach" to process each chunk as it arrives. await foreach (var res in stream) { // Get the text from the current chunk var chunkText = res.Text; usage = res.UsageMetadata; // THIS is how you see the "thinking" in real-time Console.Write(chunkText); // Append the chunk to our StringBuilder to build the final result fullResponse.Append(chunkText); } File.AppendAllText(usageLog, $"{DateTime.Now},{stopwatch.Elapsed},{tag},{model.Model}," + $"{usage?.PromptTokenCount},{usage?.ThoughtsTokenCount},{usage?.ToolUsePromptTokenCount},{usage?.CandidatesTokenCount},{usage?.TotalTokenCount}\n"); // After the loop, the StringBuilder contains the complete text. return fullResponse.ToString(); } catch (Exception ex) { await LogError(ex); } return "Failed: Please check the Error Logs"; } /// /// Logs the details of an exception to the console and appends them to an error log file. /// /// This method writes the exception message to the console for immediate visibility and appends /// detailed information, including the stack trace and inner exception (if available), to a log file named /// "gemini_errors.txt" located in the application's archive directory. /// The exception to log. Must not be . public static async Task LogError(Exception ex) { Log($"Error during query: {ex.Message}"); var errorFilePath = Path.Combine(Output, "gemini_errors.txt"); await File.AppendAllTextAsync(errorFilePath, $"\n\n\n------------------------------------------------------" + $"{DateTime.Now}\n{ex.Message}\n\n{ex.InnerException?.Message}\n\n{ex.StackTrace}"); return ex.Message; } ////private string staticModel = "gemini-2.0-flash"; ////private const string model = "gemini-2.0-flash-lite"; ////private string model = "gemini-2.0-flash"; ////private string model = "gemini-2.5-pro"; ////private string model = "gemini-2.5-pro-exp-03-25"; //private const string ApiBaseUrl = "https://generativelanguage.googleapis.com/v1beta/models/"; //private readonly string _apiKey = "AIzaSyAsphWA1WPfht3DbL2BBTa5bSknyIU1oHA"; //private class FileMetadata //{ // [JsonPropertyName("name")] // public string Name { get; set; } // [JsonPropertyName("uploadUri")] // public string UploadUri { get; set; } //} //private class FileApiResponse //{ // public FileMetadata File { get; set; } //} //public GeminiApiClient() //{ //} //public static async Task Query(string prompt, string model) //{ // var client = new GeminiApiClient(); // return await client.GenerateText(prompt, model); //} //public async Task GenerateText(string prompt, string model) //{ // using var httpClient = new HttpClient(); // httpClient.DefaultRequestHeaders.Accept.Clear(); // httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); // httpClient.Timeout = TimeSpan.FromMinutes(15); // //var requestBody = new // //{ // // contents = new[] // // { // // new // // { // // parts = new[] // // { // // new { text = prompt } // // } // // } // // } // //}; // var requestBody = new // { // contents = new[] // { // new // { // role = "user", // parts = new[] // { // new { text = prompt } // } // } // }, // generationConfig = new // { // temperature = 0 // }, // tools = new[] // { // new { googleSearch = new {}, urlContext = new{ } } // The empty object for googleSearch // } // }; // //prompt = "What is the weather right now in London UK ?"; // //var jtext = """ // // { // // "contents": [ // // { // // "role": "user", // // "parts": [ // // { // // "text": "INSERT_INPUT_HERE" // // }, // // ] // // }, // // ], // // "generationConfig": { // // "thinkingConfig": { // // "thinkingBudget": -1, // // }, // // }, // // "tools": [ // // { // // "urlContext": {} // // }, // // { // // "googleSearch": {} // // }, // // ], // // } // // """; // //var jtext = """ // // { // // "contents": [ // // { // // "role": "user", // // "parts": [ // // { // // "text": "INSERT_INPUT_HERE" // // }, // // ] // // }, // // ], // // "generationConfig": { // // "thinkingConfig": { // // "thinkingBudget": -1, // // }, // // }, // // "tools": [ // // { // // "urlContext": {} // // }, // // { // // "googleSearch": {} // // }, // // ] // // } // // """; // //string Q = "\""; // //jtext = jtext.Replace("INSERT_INPUT_HERE", prompt.Replace(Q, "'").Replace("\r", "").Replace("\n", "\\n")); // //File.WriteAllText("Debug\\LastPrompt.json", jtext); // //var requestBody = new // //{ // // contents = new[] // // { // // new // // { // // //role = "user", // // parts = new[] // // { // // new { text = prompt } // // } // // } // // }, // // generationConfig = new // // { // // temperature = 1.0, // // thinkingConfig = new // // { // // thinkingBudget = 500 // // } // // } // // //, // // //tools = new object[] // // //{ // // // new { urlContext = new {} }, // // // new { googleSearch = new {} } // // //} // //}; // File.WriteAllText("Debug\\gemini_prompt.txt", prompt); // var jsonRequestBody = JsonSerializer.Serialize(requestBody); //jtext; // // var content = new StringContent(jsonRequestBody, Encoding.UTF8, "application/json"); // File.WriteAllText("Debug\\gemini_query.json", jsonRequestBody); // var modelVersion = model.Split("/").Last(); // staticModel; // // var requestUri = $"{ApiBaseUrl}{modelVersion}:generateContent?key={_apiKey}"; // HttpResponseMessage response = await httpClient.PostAsync(requestUri, content); // // 4. Read the response content as a string // string responseContent = await response.Content.ReadAsStringAsync(); // File.WriteAllText("Debug\\gemini_response.json", responseContent); // File.AppendAllText("Debug\\gemini_response.json", responseContent + Environment.NewLine); // // Deserialize the response to extract the generated text // try // { // using (JsonDocument doc = JsonDocument.Parse(responseContent)) // { // JsonElement root = doc.RootElement; // // Navigate to the usageMetadata object. // if (root.TryGetProperty("usageMetadata", out JsonElement usageMetadata)) // { // // Safely extract each value using TryGetProperty and TryGetInt32. // // This prevents exceptions if a property is missing. // usageMetadata.TryGetProperty("promptTokenCount", out JsonElement promptTokensElement); // usageMetadata.TryGetProperty("candidatesTokenCount", out JsonElement candidatesTokensElement); // usageMetadata.TryGetProperty("totalTokenCount", out JsonElement totalTokensElement); // usageMetadata.TryGetProperty("toolUsePromptTokenCount", out JsonElement toolUseTokensElement); // // Extract the model version and response ID from the root. // root.TryGetProperty("modelVersion", out JsonElement modelVersionElement); // root.TryGetProperty("responseId", out JsonElement responseIdElement); // // Get the integer or string values, defaulting to 0 or "N/A" if not found. // int promptTokens = promptTokensElement.ValueKind == JsonValueKind.Number ? promptTokensElement.GetInt32() : 0; // int candidatesTokens = candidatesTokensElement.ValueKind == JsonValueKind.Number ? candidatesTokensElement.GetInt32() : 0; // int totalTokens = totalTokensElement.ValueKind == JsonValueKind.Number ? totalTokensElement.GetInt32() : 0; // int toolUseTokens = toolUseTokensElement.ValueKind == JsonValueKind.Number ? toolUseTokensElement.GetInt32() : 0; // string modelVersion2 = modelVersionElement.GetString() ?? "N/A"; // string responseId = responseIdElement.GetString() ?? "N/A"; // string timestamp = DateTime.UtcNow.ToString("o"); // ISO 8601 format // // --- CSV File Handling --- // // Check if the file exists to determine if we need to write the header. // string csvFilePath = "Debug\\gemini_usage_metadata.csv"; // bool fileExists = File.Exists(csvFilePath); // var csvLine = new StringBuilder(); // // If the file doesn't exist, create it and add the header row. // if (!fileExists) // { // var header = "Timestamp,ResponseId,ModelVersion,PromptTokens,CandidatesTokens,ToolUseTokens,TotalTokens"; // csvLine.AppendLine(header); // } // // Format the extracted data as a single CSV line. // csvLine.AppendFormat("\"{0}\",\"{1}\",\"{2}\",{3},{4},{5},{6}", // timestamp, // responseId, // modelVersion2, // promptTokens, // candidatesTokens, // toolUseTokens, // totalTokens); // // Asynchronously append the new line to the CSV file. // await File.AppendAllTextAsync(csvFilePath, csvLine.ToString() + Environment.NewLine); // } // // Navigate to the 'candidates' array as before // if (root.TryGetProperty("candidates", out JsonElement candidates) && candidates.GetArrayLength() > 0) // { // // Navigate to the 'content' and 'parts' of the first candidate // if (candidates[0].TryGetProperty("content", out JsonElement contentElement) && // contentElement.TryGetProperty("parts", out JsonElement parts) && // parts.GetArrayLength() > 0) // { // // 1. Use a StringBuilder for efficient string concatenation // var fullText = new System.Text.StringBuilder(); // // 2. Loop through EACH part in the 'parts' array // foreach (JsonElement part in parts.EnumerateArray()) // { // // 3. Get the text from the current part and append it // if (part.TryGetProperty("text", out JsonElement textElement)) // { // fullText.Append(textElement.GetString()); // } // } // // 4. Return the final combined string // return fullText.ToString(); // } // } // } // } // catch (JsonException ex) // { // return $"Error deserializing response: {ex.Message}"; // } // return ""; //} //public async Task GenerateTextWithAttachments(string prompt, string model, List localFilePaths) //{ // File.WriteAllText("Debug\\gemini_prompt.txt", prompt); // using var httpClient = new HttpClient(); // httpClient.DefaultRequestHeaders.Accept.Clear(); // httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); // httpClient.Timeout = TimeSpan.FromMinutes(5); // // 2. Create a list of all the parts for the request, starting with the prompt. // var allParts = new List // { // new { text = prompt } // }; // // Upload each file and add its API resource name to the request. // if (localFilePaths != null) // { // foreach (var filePath in localFilePaths) // { // try // { // string fileUriFromApi = await UploadFileAsync(filePath, _apiKey); // allParts.Add(new // { // fileData = new // { // mimeType = "text/plain", // fileUri = fileUriFromApi // } // }); // } // catch (Exception ex) // { // Console.WriteLine($"Failed to upload file {filePath}. Error: {ex.Message}"); // } // } // } // //var requestBody = new // //{ // // contents = new[] // // { // // new // // { // // role = "user", // // parts = allParts // // } // // }, // // generationConfig = new // // { // // temperature = 0 // // } // //}; // var requestBody = new // { // contents = new[] // { // new // { // role = "user", // parts = allParts // } // }, // generationConfig = new // { // temperature = 0 // }, // tools = new[] // { // new { googleSearch = new {}, urlContext = new { } } // The empty object for googleSearch // } // }; // // // // new { googleSearch = new { }, urlContext = new { } } // The empty object for googleSearch // var jsonRequestBody = JsonSerializer.Serialize(requestBody); //jtext; // // var content = new StringContent(jsonRequestBody, Encoding.UTF8, "application/json"); // File.WriteAllText("Debug\\gemini_query.json", jsonRequestBody); // var modelVersion = model.Split("/").Last(); // staticModel; // // var requestUri = $"{ApiBaseUrl}{modelVersion}:generateContent?key={_apiKey}"; // int waitTime = 60 * 1000; // 1 minute in milliseconds // HttpResponseMessage response = null; // for (int attempt = 1; attempt <= 50; attempt++) // { // try // { // response = await httpClient.PostAsync(requestUri, content); // response.EnsureSuccessStatusCode(); // Throw if not a success code. // break; // Exit the loop if successful. // } // catch (HttpRequestException ex) when (attempt < 50) // More specific // { // Console.WriteLine($"Attempt {attempt} failed: {ex.Message}. Retrying..."); // await Task.Delay(waitTime); // } // catch (Exception ex) when (attempt < 50) // { // Console.WriteLine($"Attempt {attempt} failed: {ex.Message}. Retrying..."); // await Task.Delay(waitTime); // Wait before retrying. // } // } // // 4. Read the response content as a string // string responseContent = await response.Content.ReadAsStringAsync(); // File.WriteAllText("Debug\\gemini_response.json", responseContent); // // Deserialize the response to extract the generated text // try // { // using (JsonDocument doc = JsonDocument.Parse(responseContent)) // { // JsonElement root = doc.RootElement; // // Navigate to the usageMetadata object. // if (root.TryGetProperty("usageMetadata", out JsonElement usageMetadata)) // { // // Safely extract each value using TryGetProperty and TryGetInt32. // // This prevents exceptions if a property is missing. // usageMetadata.TryGetProperty("promptTokenCount", out JsonElement promptTokensElement); // usageMetadata.TryGetProperty("candidatesTokenCount", out JsonElement candidatesTokensElement); // usageMetadata.TryGetProperty("totalTokenCount", out JsonElement totalTokensElement); // usageMetadata.TryGetProperty("toolUsePromptTokenCount", out JsonElement toolUseTokensElement); // // Extract the model version and response ID from the root. // root.TryGetProperty("modelVersion", out JsonElement modelVersionElement); // root.TryGetProperty("responseId", out JsonElement responseIdElement); // // Get the integer or string values, defaulting to 0 or "N/A" if not found. // int promptTokens = promptTokensElement.ValueKind == JsonValueKind.Number ? promptTokensElement.GetInt32() : 0; // int candidatesTokens = candidatesTokensElement.ValueKind == JsonValueKind.Number ? candidatesTokensElement.GetInt32() : 0; // int totalTokens = totalTokensElement.ValueKind == JsonValueKind.Number ? totalTokensElement.GetInt32() : 0; // int toolUseTokens = toolUseTokensElement.ValueKind == JsonValueKind.Number ? toolUseTokensElement.GetInt32() : 0; // string modelVersion2 = modelVersionElement.GetString() ?? "N/A"; // string responseId = responseIdElement.GetString() ?? "N/A"; // string timestamp = DateTime.UtcNow.ToString("o"); // ISO 8601 format // // --- CSV File Handling --- // // Check if the file exists to determine if we need to write the header. // string csvFilePath = "Debug\\gemini_usage_metadata.csv"; // bool fileExists = File.Exists(csvFilePath); // var csvLine = new StringBuilder(); // // If the file doesn't exist, create it and add the header row. // if (!fileExists) // { // var header = "Timestamp,ResponseId,ModelVersion,PromptTokens,CandidatesTokens,ToolUseTokens,TotalTokens"; // csvLine.AppendLine(header); // } // // Format the extracted data as a single CSV line. // csvLine.AppendFormat("\"{0}\",\"{1}\",\"{2}\",{3},{4},{5},{6}", // timestamp, // responseId, // modelVersion2, // promptTokens, // candidatesTokens, // toolUseTokens, // totalTokens); // // Asynchronously append the new line to the CSV file. // await File.AppendAllTextAsync(csvFilePath, csvLine.ToString() + Environment.NewLine); // } // // Navigate to the 'candidates' array as before // if (root.TryGetProperty("candidates", out JsonElement candidates) && candidates.GetArrayLength() > 0) // { // // Navigate to the 'content' and 'parts' of the first candidate // if (candidates[0].TryGetProperty("content", out JsonElement contentElement) && // contentElement.TryGetProperty("parts", out JsonElement parts) && // parts.GetArrayLength() > 0) // { // // 1. Use a StringBuilder for efficient string concatenation // var fullText = new System.Text.StringBuilder(); // // 2. Loop through EACH part in the 'parts' array // foreach (JsonElement part in parts.EnumerateArray()) // { // // 3. Get the text from the current part and append it // if (part.TryGetProperty("text", out JsonElement textElement)) // { // fullText.Append(textElement.GetString()); // } // } // // 4. Return the final combined string // return fullText.ToString(); // } // } // } // } // catch (JsonException ex) // { // return $"Error deserializing response: {ex.Message}"; // } // return ""; //} ///// ///// Uploads a local file to the Gemini File API. ///// ///// The full path to the local file. ///// Your Google AI API key. ///// The resource name of the uploaded file (e.g., "files/your-file-id"). ///// Thrown if the local file does not exist. ///// Thrown if an API call fails. //private async Task UploadFileAsync(string filePath, string apiKey) //{ // if (!File.Exists(filePath)) // throw new FileNotFoundException($"File not found at path: {filePath}"); // // Step 1: Start resumable upload // var metadataUploadUri = $"https://generativelanguage.googleapis.com/upload/v1beta/files?key={apiKey}"; // using var httpClient = new HttpClient { Timeout = TimeSpan.FromMinutes(5) }; // var fileInfo = new FileInfo(filePath); // httpClient.DefaultRequestHeaders.Add("X-Goog-Upload-Protocol", "resumable"); // httpClient.DefaultRequestHeaders.Add("X-Goog-Upload-Command", "start"); // httpClient.DefaultRequestHeaders.Add("X-Goog-Upload-Header-Content-Length", fileInfo.Length.ToString()); // httpClient.DefaultRequestHeaders.Add("X-Goog-Upload-Header-Content-Type", "text/plain"); // // 👇 empty body – don’t send JSON metadata here // var startResponse = await httpClient.PostAsync(metadataUploadUri, new StringContent(string.Empty)); // startResponse.EnsureSuccessStatusCode(); // File.WriteAllText("Debug\\file_start_response.json", startResponse.Content.ToString()); // // Upload URL comes from response headers // string uploadUrl = startResponse.Headers.GetValues("X-Goog-Upload-URL").First(); // // Step 2: Upload file contents // byte[] fileBytes = await File.ReadAllBytesAsync(filePath); // using var uploadClient = new HttpClient(); // var byteContent = new ByteArrayContent(fileBytes); // byteContent.Headers.ContentType = new MediaTypeHeaderValue("text/plain"); // uploadClient.DefaultRequestHeaders.Add("X-Goog-Upload-Command", "upload, finalize"); // uploadClient.DefaultRequestHeaders.Add("X-Goog-Upload-Offset", "0"); // var uploadResponse = await uploadClient.PostAsync(uploadUrl, byteContent); // uploadResponse.EnsureSuccessStatusCode(); // string responseBody = await uploadResponse.Content.ReadAsStringAsync(); // File.WriteAllText("Debug\\file_upload_response.json", responseBody); // // Parse the JSON and extract just the "name" field // using JsonDocument doc = JsonDocument.Parse(responseBody); // if (doc.RootElement.TryGetProperty("file", out JsonElement fileElement) && fileElement.TryGetProperty("uri", out JsonElement nameElement)) // { // string fileUri = nameElement.GetString(); // e.g. "files/flmbrerlgbfp" // Console.WriteLine($"Successfully uploaded {Path.GetFileName(filePath)} as {fileUri}"); // return fileUri; // } // throw new InvalidOperationException("Upload response did not contain 'file.name'. Response: " + responseBody); //} }