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;
}
}