adding new mindforge applications
All checks were successful
Mindforge API Build and Deploy / Build Mindforge API Image (push) Successful in 1m8s
Mindforge Cronjob Build and Deploy / Build Mindforge Cronjob Image (push) Successful in 1m19s
Mindforge API Build and Deploy / Deploy Mindforge API (internal) (push) Successful in 11s
Mindforge Cronjob Build and Deploy / Deploy Mindforge Cronjob (internal) (push) Successful in 10s
Mindforge Web Build and Deploy (internal) / Build Mindforge Web Image (push) Successful in 2m25s
Mindforge Web Build and Deploy (internal) / Deploy Mindforge Web (internal) (push) Successful in 12s
All checks were successful
Mindforge API Build and Deploy / Build Mindforge API Image (push) Successful in 1m8s
Mindforge Cronjob Build and Deploy / Build Mindforge Cronjob Image (push) Successful in 1m19s
Mindforge API Build and Deploy / Deploy Mindforge API (internal) (push) Successful in 11s
Mindforge Cronjob Build and Deploy / Deploy Mindforge Cronjob (internal) (push) Successful in 10s
Mindforge Web Build and Deploy (internal) / Build Mindforge Web Image (push) Successful in 2m25s
Mindforge Web Build and Deploy (internal) / Deploy Mindforge Web (internal) (push) Successful in 12s
This commit is contained in:
202
Mindforge.API/Providers/GeminiApiProvider.cs
Normal file
202
Mindforge.API/Providers/GeminiApiProvider.cs
Normal file
@@ -0,0 +1,202 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Mindforge.API.Providers
|
||||
{
|
||||
public class GeminiApiProvider : ILlmApiProvider
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ILogger<GeminiApiProvider> _logger;
|
||||
|
||||
public GeminiApiProvider(HttpClient httpClient, IConfiguration configuration, ILogger<GeminiApiProvider> logger)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_httpClient.Timeout = TimeSpan.FromMinutes(5);
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<string> SendRequestAsync(string systemPrompt, string userPrompt, string model)
|
||||
{
|
||||
var apiKey = _configuration["GEMINI_API_KEY"];
|
||||
if (string.IsNullOrEmpty(apiKey))
|
||||
{
|
||||
throw new Exception("GEMINI_API_KEY not found in configuration.");
|
||||
}
|
||||
|
||||
var apiBase = "https://generativelanguage.googleapis.com/v1beta";
|
||||
var url = $"{apiBase.TrimEnd('/')}/models/{model}:generateContent?key={apiKey}";
|
||||
|
||||
var reqBody = new
|
||||
{
|
||||
system_instruction = string.IsNullOrEmpty(systemPrompt) ? null : new
|
||||
{
|
||||
parts = new[] { new { text = systemPrompt } }
|
||||
},
|
||||
contents = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
role = "user",
|
||||
parts = new[] { new { text = userPrompt } }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var jsonBody = JsonSerializer.Serialize(reqBody, new JsonSerializerOptions { DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull });
|
||||
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, url);
|
||||
request.Content = new StringContent(jsonBody, Encoding.UTF8, "application/json");
|
||||
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
var responseBody = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
throw new Exception($"Gemini API error status {(int)response.StatusCode}: {responseBody}");
|
||||
}
|
||||
|
||||
var result = JsonSerializer.Deserialize<JsonElement>(responseBody);
|
||||
if (result.TryGetProperty("candidates", out var candidates) && candidates.GetArrayLength() > 0)
|
||||
{
|
||||
var content = candidates[0].GetProperty("content");
|
||||
if (content.TryGetProperty("parts", out var parts) && parts.GetArrayLength() > 0)
|
||||
{
|
||||
return parts[0].GetProperty("text").GetString() ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception("empty response from Gemini API");
|
||||
}
|
||||
|
||||
public async Task<string> SendRequestBatchAsync(string systemPrompt, string userPrompt, string model)
|
||||
{
|
||||
var apiKey = _configuration["GEMINI_API_KEY"];
|
||||
if (string.IsNullOrEmpty(apiKey))
|
||||
throw new Exception("GEMINI_API_KEY not found in configuration.");
|
||||
|
||||
var apiBase = "https://generativelanguage.googleapis.com/v1beta";
|
||||
var jsonOptions = new JsonSerializerOptions { DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull };
|
||||
|
||||
// Build single inline request
|
||||
var batchBody = new
|
||||
{
|
||||
batch = new
|
||||
{
|
||||
display_name = "mindforge-batch",
|
||||
input_config = new
|
||||
{
|
||||
requests = new
|
||||
{
|
||||
requests = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
request = new
|
||||
{
|
||||
system_instruction = string.IsNullOrEmpty(systemPrompt) ? null : new
|
||||
{
|
||||
parts = new[] { new { text = systemPrompt } }
|
||||
},
|
||||
contents = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
role = "user",
|
||||
parts = new[] { new { text = userPrompt } }
|
||||
}
|
||||
}
|
||||
},
|
||||
metadata = new { key = "request-1" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Submit batch job
|
||||
var createUrl = $"{apiBase}/models/{model}:batchGenerateContent?key={apiKey}";
|
||||
using var createReq = new HttpRequestMessage(HttpMethod.Post, createUrl);
|
||||
createReq.Content = new StringContent(JsonSerializer.Serialize(batchBody, jsonOptions), Encoding.UTF8, "application/json");
|
||||
|
||||
var createResp = await _httpClient.SendAsync(createReq);
|
||||
var createBody = await createResp.Content.ReadAsStringAsync();
|
||||
if (!createResp.IsSuccessStatusCode)
|
||||
throw new Exception($"Gemini Batch API error creating job {(int)createResp.StatusCode}: {createBody}");
|
||||
|
||||
_logger.LogInformation("Gemini Batch API job created");
|
||||
|
||||
var createResult = JsonSerializer.Deserialize<JsonElement>(createBody);
|
||||
if (!createResult.TryGetProperty("name", out var nameEl))
|
||||
throw new Exception("Gemini Batch API did not return a job name.");
|
||||
|
||||
var batchName = nameEl.GetString()!;
|
||||
var pollUrl = $"{apiBase}/{batchName}?key={apiKey}";
|
||||
|
||||
_logger.LogInformation("Gemini Batch API job name: {BatchName}", batchName);
|
||||
|
||||
// Poll until terminal state
|
||||
while (true)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(10));
|
||||
|
||||
using var pollReq = new HttpRequestMessage(HttpMethod.Get, pollUrl);
|
||||
var pollResp = await _httpClient.SendAsync(pollReq);
|
||||
var pollBody = await pollResp.Content.ReadAsStringAsync();
|
||||
|
||||
if (!pollResp.IsSuccessStatusCode)
|
||||
throw new Exception($"Gemini Batch API error polling status {(int)pollResp.StatusCode}: {pollBody}");
|
||||
|
||||
var pollResult = JsonSerializer.Deserialize<JsonElement>(pollBody);
|
||||
var metadata = pollResult.GetProperty("metadata");
|
||||
var state = metadata.GetProperty("state");
|
||||
|
||||
_logger.LogInformation("Gemini Batch API job state: {State}", state.GetString());
|
||||
|
||||
switch (state.GetString())
|
||||
{
|
||||
case "BATCH_STATE_SUCCEEDED":
|
||||
if (pollResult.TryGetProperty("response", out var batchResponse) &&
|
||||
batchResponse.TryGetProperty("inlinedResponses", out var inlinedResponses) &&
|
||||
inlinedResponses.TryGetProperty("inlinedResponses", out var inlinedResponsesInternal) &&
|
||||
inlinedResponsesInternal.GetArrayLength() > 0)
|
||||
{
|
||||
_logger.LogInformation("Gemini Batch API job succeeded");
|
||||
|
||||
var first = inlinedResponsesInternal[0];
|
||||
if (first.TryGetProperty("error", out var reqError))
|
||||
throw new Exception($"Gemini Batch request error: {reqError}");
|
||||
|
||||
if (first.TryGetProperty("response", out var innerResponse) &&
|
||||
innerResponse.TryGetProperty("candidates", out var candidates) &&
|
||||
candidates.GetArrayLength() > 0)
|
||||
{
|
||||
var content = candidates[0].GetProperty("content");
|
||||
if (content.TryGetProperty("parts", out var parts) && parts.GetArrayLength() > 0)
|
||||
return parts[0].GetProperty("text").GetString() ?? string.Empty;
|
||||
}
|
||||
}
|
||||
throw new Exception("Gemini Batch job succeeded but returned no content.");
|
||||
|
||||
case "BATCH_STATE_FAILED":
|
||||
throw new Exception($"Gemini Batch job failed: {pollBody}");
|
||||
|
||||
case "BATCH_STATE_CANCELLED":
|
||||
throw new Exception("Gemini Batch job was cancelled.");
|
||||
|
||||
case "BATCH_STATE_EXPIRED":
|
||||
throw new Exception("Gemini Batch job expired before completing.");
|
||||
|
||||
// BATCH_STATE_PENDING / BATCH_STATE_RUNNING — keep polling
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user