using AiAgent.Core.Infrastructure; using Mscc.GenerativeAI; using System.Text.Json; using static AiAgent.Core.Constant.ProgramConstants; using static AiAgent.Core.Helpers.GenericHelper; namespace AiAgent.Core.Agents.Base; public class BaseAgent() { /// /// Gets the default configuration for a generative model. /// public static GenerativeModel GetBaseModel => new() { Model = GeminiFlashLatest, //GeminiPro25, UseGoogleSearch = true, UseGrounding = false, UseCodeExecution = false, UseJsonMode = false, Timeout = TimeSpan.FromMinutes(60) }; /// /// Gets the default base configuration for generation settings. /// public static GenerationConfig GetBaseConfig => new() { Temperature = 0.0f, CandidateCount = 1, ThinkingConfig = new ThinkingConfig { ThinkingBudget = -1, //24576, //32000, IncludeThoughts = true, }, StopSequences= ["\r\n\\\r\n\\\r\n\\\r\n\\"] }; /// /// Validates the existence of required files and determines whether the operation can proceed. /// /// This method performs a series of checks to ensure that the necessary files are in the correct /// state before proceeding with the operation. It logs messages to indicate the reason for any failure or skipped /// execution. /// The name of the operation, used for logging purposes. /// The path to the prompt file. The operation will only proceed if this file exists. /// The path to the output file. The operation will only proceed if this file does not already exist. /// An optional list of paths to supporting input files. The operation will only proceed if all specified files /// exist. /// A value indicating the outcome of the validation: /// if the prompt file or any required supporting file does not /// exist. if the output file already /// exists. if all required conditions are met /// and the operation can proceed. public static Results CheckDocuments(string name, string? briefFile, string? outputFile, List? supportingDocuments = null) { Log($"\n\n==========================================================================="); Log($"Starting the {name}"); Log($"---------------------------------\n"); // Check the prompt, we only work if the prompt exists if (briefFile != null) if (File.Exists(briefFile) is false) { Log($"- Not doing anything as the input file does not exist: {briefFile}"); return Results.Failed; } // Check the output, we only work if the output does not exist if (outputFile != null) if (File.Exists(outputFile)) { Log($"- Not doing anything as the output file already exists: {outputFile}"); return Results.NotExecuted; } // We only work if the supporting documents exist if (supportingDocuments != null) foreach (var file in supportingDocuments) if (File.Exists(file) is false) { Log($"- Not doing anything as the supporting document does not exist: {file}"); return Results.Failed; } // Now we can work return Results.Success; } /// /// Executes a generative model query using the specified configuration, input prompt, and optional input data /// files, and writes the generated output to a file. /// /// This method attempts to execute the query using multiple API keys in sequence. If all /// attempts fail, the method returns . Any errors encountered during execution are /// logged. /// The generative model to use for the query. The model's API key will be set during execution. /// The configuration settings for the generation process. /// The name associated with the query, used for identification purposes. /// The path to the file containing the input prompt for the generative model. /// The path to the file where the generated output will be written. /// An optional list of file paths containing additional input data for the query. Can be . /// A value indicating the outcome of the operation. Returns if /// the query completes successfully and the output is written to the file; otherwise, returns . public static async Task RunAsync(GenerativeModel model, GenerationConfig confi, string apiKey, string name, string promptFile, string outputFile, List? inputDataFiles = null, string[]? jsonMandatoryFields = null) { // Make sure that everything is in place in order to do some work var results = CheckDocuments(name, promptFile, outputFile, inputDataFiles); // Anything but success is necessary if (results != Results.Success) return results; for (int tries = 0; tries < 5; tries++) { // Get the model core name Log($"- Starting Gemini Query with model: {model.Model}\n"); // Get the prompt string prompt = await File.ReadAllTextAsync(promptFile); // Query var response = await GeminiApiClient.Query(prompt, name, model, confi, apiKey, inputDataFiles); // Check for failure if (response.Contains("\"status\": \"FAILURE\"")) { Log($"-> Gemini Query failed:\n\n{response}"); if (response.Contains("\"reason_code\": \"RESEARCH_TOOL_UNAVAILABLE\"")) { await Task.Delay(5000); continue; } } // Check for json tags if (jsonMandatoryFields is not null) { // Isolate the JSON part if (response.Contains("```json")) response = response.Split("```json", StringSplitOptions.TrimEntries).Last(); if (response.Contains("```")) response = response.Split("```", StringSplitOptions.TrimEntries).First(); try { var options = new JsonDocumentOptions { AllowTrailingCommas = true, CommentHandling = JsonCommentHandling.Skip }; JsonDocument doc = JsonDocument.Parse(response, options); JsonElement root = doc.RootElement; // Parse the JSON to make sure it is valid and contains the mandatory fields foreach (string field in jsonMandatoryFields) if (!root.TryGetProperty(field, out _)) throw new Exception($"Mandatory field '{field}' not found in the JSON response."); Log($"\n\nThe response is a valid JSON with all mandatory fields: {string.Join(", ", jsonMandatoryFields)}"); } catch (Exception ex) { Log($"- JSON Parsing failed: {ex.Message}"); await Task.Delay(120000); continue; } } // Write the output if (response.Length > 0) { File.WriteAllText(outputFile, response); return Results.Success; } Log($"- Gemini Query attempt {tries + 1} failed, trying again..."); await Task.Delay(2000); } throw new Exception("All attempts to run the Gemini Query have failed."); return Results.Failed; } /// /// Executes the average worker process using the specified input and output files, and an optional list of /// additional data files. /// /// This method validates the provided files and configuration before executing the worker /// process. If the validation fails, the method returns the corresponding error result without performing the /// operation. /// The name of the worker process. This is used to identify the operation. /// The path to the prompt file that contains the input instructions for the worker. /// The path to the output file where the results of the operation will be written. /// An optional list of paths to additional input data files required for the operation. Can be if no additional files are needed. /// A value indicating the outcome of the operation. Returns if /// the operation completes successfully; otherwise, returns an appropriate error code. public static async Task RunAverageWorker(string name, string promptFile, string outputFile, List? inputDataFiles = null, bool jsonOutput = true) { // Make sure that everything is in place in order to do some work var results = CheckDocuments(name, promptFile, outputFile, inputDataFiles); // Anything but success is necessary if (results != Results.Success) return results; // Launch the average worker with the default average base configuration var response = await RunAsync(GetBaseModel, GetBaseConfig, ApiKeys.GetNextFreeKey(), name, promptFile, outputFile, inputDataFiles); return response; } /// /// Executes the average worker process using the specified input and output files, and an optional list of /// additional data files. /// /// This method validates the provided files and configuration before executing the worker /// process. If the validation fails, the method returns the corresponding error result without performing the /// operation. /// The name of the worker process. This is used to identify the operation. /// The path to the prompt file that contains the input instructions for the worker. /// The path to the output file where the results of the operation will be written. /// An optional list of paths to additional input data files required for the operation. Can be if no additional files are needed. /// A value indicating the outcome of the operation. Returns if /// the operation completes successfully; otherwise, returns an appropriate error code. public static async Task RunAverageOfflineWorker(string name, string promptFile, string outputFile, List? inputDataFiles = null, bool jsonOutput = true) { // Make sure that everything is in place in order to do some work var results = CheckDocuments(name, promptFile, outputFile, inputDataFiles); // Anything but success is necessary if (results != Results.Success) return results; GenerativeModel model = GetBaseModel; model.UseGoogleSearch = false; model.UseGrounding = false; // Launch the average worker with the default average base configuration var response = await RunAsync(model, GetBaseConfig, ApiKeys.GetNextFreeKey(), name, promptFile, outputFile, inputDataFiles); return response; } /// /// Executes the Clever Offline Worker process using the specified parameters. /// /// This method performs a series of checks to ensure that all required files and configurations /// are in place before executing the worker process. It uses a generative model with a predefined configuration to /// process the input and generate the output. /// The name of the task or operation to be executed. /// The path to the prompt file that contains the instructions for the worker. /// The path to the file where the output of the operation will be saved. /// An optional list of input data file paths to be used during the operation. Can be if no /// input files are required. /// A value indicating whether the output should be formatted as JSON. The default value is . /// A enumeration value indicating the outcome of the operation. Returns if the operation completes successfully; otherwise, returns an appropriate error code. public static async Task RunCleverOfflineWorker(string name, string promptFile, string outputFile, List? inputDataFiles = null, bool jsonOutput = true) { // Make sure that everything is in place in order to do some work var results = CheckDocuments(name, promptFile, outputFile, inputDataFiles); // Anything but success is necessary if (results != Results.Success) return results; GenerativeModel model = GetBaseModel; model.Model = GeminiPro25; model.UseGoogleSearch = false; model.UseGrounding = false; // Launch the average worker with the default average base configuration var response = await RunAsync(model, GetBaseConfig, ApiKeys.GetNextFreeKey(), name, promptFile, outputFile, inputDataFiles); return response; } /// /// Executes a generative worker task using the specified configuration and input files. /// /// This method validates the provided files and configuration before executing the task. It uses /// a generative model with default settings, including Google Search and grounding enabled. Ensure that all /// required files are accessible and properly formatted before calling this method. /// The name of the task to be executed. This is used to identify the operation. /// The path to the prompt file that contains the instructions or input for the generative model. /// The path to the file where the output of the operation will be saved. /// An optional list of paths to additional input data files required for the operation. Can be if no additional input files are needed. /// A value indicating whether the output should be formatted as JSON. The default value is . /// A enumeration value indicating the outcome of the operation. Returns if the operation completes successfully; otherwise, returns an appropriate error /// result. public static async Task RunCleverWorker(string name, string promptFile, string outputFile, List? inputDataFiles = null, bool jsonOutput = true) { // Make sure that everything is in place in order to do some work var results = CheckDocuments(name, promptFile, outputFile, inputDataFiles); // Anything but success is necessary if (results != Results.Success) return results; GenerativeModel model = GetBaseModel; model.Model = GeminiPro25; model.UseGoogleSearch = true; model.UseGrounding = true; // Launch the average worker with the default average base configuration var response = await RunAsync(GetBaseModel, GetBaseConfig, ApiKeys.GetNextFreeKey(), name, promptFile, outputFile, inputDataFiles); return response; } }