* Code refactoring
This commit is contained in:
@@ -1,108 +1,95 @@
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Gitea.Net.Api;
|
||||
using Gitea.Net.Client;
|
||||
using Gitea.Net.Model;
|
||||
using OpenAI;
|
||||
using OpenAI.Chat;
|
||||
using PipelineAgent.ChangesChecker.Models;
|
||||
using Services.Gitea;
|
||||
using Services.OpenAI;
|
||||
using Services.Vault;
|
||||
|
||||
namespace PipelineAgent.ChangesChecker;
|
||||
|
||||
public class ChangesCheckerAgent
|
||||
public class ChangesCheckerAgent(IOpenAiService openAiService, IGiteaService giteaService) : IChangesCheckerAgent
|
||||
{
|
||||
public async Task<int> CheckChangesAsync()
|
||||
public async Task<int> CheckChangesAsync(string repositoryPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<string> lastChangesFromGitea = await GetLastChangesFromGitea();
|
||||
var vaultService = CreateVaultService();
|
||||
var giteaApiToken = await vaultService.GetSecretAsync("api_keys/gitea", "gitea_api_token_write");
|
||||
var giteaConfiguration = GetGiteaConfiguration(repositoryPath, giteaApiToken);
|
||||
var giteaClient = giteaService.CreateGiteaClient(giteaConfiguration);
|
||||
var lastChangesFromGitea = await GetLastChangesFromGiteaAsync(giteaClient, giteaConfiguration);
|
||||
|
||||
var userContent = JsonSerializer.Serialize(lastChangesFromGitea);
|
||||
|
||||
var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY");
|
||||
if (string.IsNullOrWhiteSpace(apiKey))
|
||||
if (lastChangesFromGitea.Count == 0)
|
||||
{
|
||||
Console.WriteLine("AI‑Gate: OPENAI_API_KEY not set, blocking by default.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
var chatRequest = lastChangesFromGitea.Select(x => new ChatRequest(x.Key, x.Value));
|
||||
var prompt = await File.ReadAllTextAsync(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Prompts",
|
||||
"ChangesChecker.txt"));
|
||||
|
||||
var decisionDoc = await openAiService.GetResponseFromChat(chatRequest, prompt);
|
||||
|
||||
if (decisionDoc == null)
|
||||
{
|
||||
Console.WriteLine("AI‑Gate: no response from LLM, blocking by default.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
var client = new OpenAIClient(apiKey);
|
||||
|
||||
var systemPrompt = """
|
||||
Jesteś Senior .NET Developerem z kilkunastoletnim stażem.
|
||||
Na podstawie listy zawartości plików zmienionych w ostatnich commitach:
|
||||
|
||||
- zdekoduj zawartość pliku zapisaną w base64
|
||||
- sprwadź kod pod kątem poprawności i potencjalnych błędów
|
||||
- zasugeruj poprawki lub ulepszenia, jeśli to konieczne, tak, żeby kod był jak najbardziej optymalny i profesjonalny
|
||||
- zwróć JEDEN obiekt JSON w formacie:
|
||||
{
|
||||
"file-content": "zdekodowana zawartość pliku",
|
||||
"improvements": "wprowadzone poprawki w kodzie lub 'brak' w jezyku polskim",
|
||||
"changed-file" "nowy plik z wprowadzonymi poprawkami lub 'brak'",
|
||||
"decision": "approve" | "changes-requested"
|
||||
}
|
||||
|
||||
Decyzja:
|
||||
- "approve" gdy kod jest w porzadku i nie potrzebuje zadnych zmian.
|
||||
- "changes-requested" gdy kod wymaga poprawek lub ulepszeń.
|
||||
Nie dodawaj żadnego tekstu poza JSON.
|
||||
""";
|
||||
|
||||
// var userContent = JsonSerializer.Serialize(failuresForModel);
|
||||
//
|
||||
var chat = client.GetChatClient("gpt-4.1-mini");
|
||||
|
||||
var response = await chat.CompleteChatAsync(new SystemChatMessage(systemPrompt),
|
||||
new UserChatMessage($"Pliki ostatnio zmienione:\n{userContent}"));
|
||||
|
||||
var json = response.Value.Content[0].Text;
|
||||
Console.WriteLine("AI‑Gate LLM raw response:");
|
||||
Console.WriteLine(json);
|
||||
|
||||
var decisionDoc = JsonDocument.Parse(json);
|
||||
var decision = decisionDoc.RootElement.GetProperty("decision").GetString();
|
||||
var improvements = decisionDoc.RootElement.GetProperty("improvements").GetString();
|
||||
var fileContent = decisionDoc.RootElement.GetProperty("file-content").GetString();
|
||||
var changedFile = decisionDoc.RootElement.GetProperty("changed-file").GetString();
|
||||
|
||||
Console.WriteLine($"AI‑Gate decision: {decision}");
|
||||
Console.WriteLine($"Improvements: {improvements}");
|
||||
Console.WriteLine($"FileContent: {fileContent}");
|
||||
Console.WriteLine($"ChangedFile: {changedFile}");
|
||||
//
|
||||
// return decision switch
|
||||
// {
|
||||
// "allow" => 0,
|
||||
// "allow-with-warning" => 0,
|
||||
// _ => 1
|
||||
// };
|
||||
if (!decisionDoc.RootElement.TryGetProperty("changes", out var changesElement) ||
|
||||
changesElement.ValueKind == JsonValueKind.Null) return 0;
|
||||
|
||||
return 0;
|
||||
var content = GetFormattedJsonRequest(changesElement);
|
||||
var (status, response) = await giteaService.SendRequestAsync(giteaConfiguration, "contents", content);
|
||||
|
||||
if (status)
|
||||
{
|
||||
Console.WriteLine($"AI‑Gate: created PR with suggested changes.");
|
||||
Console.WriteLine(response);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("AI‑Gate: failed to create PR with suggested changes.");
|
||||
Console.WriteLine(response);
|
||||
}
|
||||
|
||||
return decision switch
|
||||
{
|
||||
"approve" => 0,
|
||||
_ => 1
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"AI‑Gate: error while reading TRX: {ex.Message}");
|
||||
Console.WriteLine($"AI‑Gate: while processing: {ex.Message}");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<List<string>> GetLastChangesFromGitea()
|
||||
private async Task<Dictionary<string, string>> GetLastChangesFromGiteaAsync(RepositoryApi giteaClient,
|
||||
GiteaConfiguration giteaConfiguration)
|
||||
{
|
||||
var lastChanges = new List<string>();
|
||||
var lastChanges = new Dictionary<string, string>();
|
||||
var lastCommit = await giteaService.GetLastCommitAsync(giteaClient, giteaConfiguration);
|
||||
|
||||
RepositoryApi repositoryApi = await CreateGiteaClient();
|
||||
if (lastCommit == null || lastCommit.VarCommit.Message.Contains("LLM: Code review suggestions"))
|
||||
{
|
||||
return lastChanges;
|
||||
}
|
||||
|
||||
var lastCommits = await repositoryApi.RepoGetAllCommitsAsync("FA", "FA", "master", limit: 2);
|
||||
var commitAffectedFilesList = lastCommits.SelectMany(x => x.Files).ToList();
|
||||
|
||||
foreach (CommitAffectedFiles commitAffectedFile in commitAffectedFilesList)
|
||||
foreach (CommitAffectedFiles commitAffectedFile in lastCommit.Files)
|
||||
{
|
||||
var repoGetContentsAsync =
|
||||
await repositoryApi.RepoGetContentsAsync("FA", "FA", commitAffectedFile.Filename);
|
||||
await giteaService.GetFileContentAsync(giteaClient, giteaConfiguration, commitAffectedFile.Filename);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(repoGetContentsAsync.Content))
|
||||
if (!string.IsNullOrWhiteSpace(repoGetContentsAsync.Content) &&
|
||||
!lastChanges.ContainsKey(commitAffectedFile.Filename))
|
||||
{
|
||||
lastChanges.Add(repoGetContentsAsync.Content);
|
||||
lastChanges.Add(commitAffectedFile.Filename, Base64Decode(repoGetContentsAsync.Content));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,21 +103,64 @@ public class ChangesCheckerAgent
|
||||
return new VaultService(vaultUrl, vaultToken);
|
||||
}
|
||||
|
||||
private async Task<RepositoryApi> CreateGiteaClient()
|
||||
private GiteaConfiguration GetGiteaConfiguration(string repositoryPath, string giteaApiToken)
|
||||
{
|
||||
VaultService vaultService = CreateVaultService();
|
||||
string giteaApiToken = await vaultService.GetSecretAsync("api_keys/gitea", "gitea_api_token", "secret") ??
|
||||
string.Empty;
|
||||
string owner = repositoryPath.Split("/").SkipLast(1).Last();
|
||||
string repository = repositoryPath.Split("/").Last();
|
||||
string branch = "master";
|
||||
string host = "https://git.modwad.pl";
|
||||
|
||||
var config = new Configuration
|
||||
return new GiteaConfiguration
|
||||
{
|
||||
BasePath = "https://git.modwad.pl/api/v1",
|
||||
ApiKey =
|
||||
Owner = owner,
|
||||
Repository = repository,
|
||||
Branch = branch,
|
||||
ApiToken = giteaApiToken,
|
||||
Host = host
|
||||
};
|
||||
}
|
||||
|
||||
private string Base64Decode(string base64EncodedData)
|
||||
{
|
||||
byte[] bytes = Convert.FromBase64String(base64EncodedData);
|
||||
return Encoding.UTF8.GetString(bytes);
|
||||
}
|
||||
|
||||
private StringContent GetFormattedJsonRequest(JsonElement json)
|
||||
{
|
||||
var branch = json.GetProperty("branch").GetString();
|
||||
var newBranch = json.GetProperty("new_branch").GetString();
|
||||
var message = json.GetProperty("message").GetString();
|
||||
|
||||
var filesJson = json.GetProperty("files");
|
||||
var files = new List<object>();
|
||||
|
||||
foreach (var fileEl in filesJson.EnumerateArray())
|
||||
{
|
||||
var operation = fileEl.GetProperty("operation").GetString();
|
||||
var path = fileEl.GetProperty("path").GetString();
|
||||
var contentPlain = fileEl.GetProperty("content").GetString() ?? string.Empty;
|
||||
|
||||
var contentBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(contentPlain));
|
||||
|
||||
files.Add(new
|
||||
{
|
||||
["token"] = giteaApiToken
|
||||
}
|
||||
operation,
|
||||
path,
|
||||
content = contentBase64
|
||||
});
|
||||
}
|
||||
|
||||
var payload = new
|
||||
{
|
||||
branch,
|
||||
new_branch = newBranch,
|
||||
message,
|
||||
files
|
||||
};
|
||||
|
||||
return new RepositoryApi(config);
|
||||
var jsonPayload = JsonSerializer.Serialize(payload);
|
||||
|
||||
return new StringContent(jsonPayload, Encoding.UTF8, "application/json");
|
||||
}
|
||||
}
|
||||
6
PipelineAgent/ChangesChecker/IChangesCheckerAgent.cs
Normal file
6
PipelineAgent/ChangesChecker/IChangesCheckerAgent.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace PipelineAgent.ChangesChecker;
|
||||
|
||||
public interface IChangesCheckerAgent
|
||||
{
|
||||
Task<int> CheckChangesAsync(string repositoryPath);
|
||||
}
|
||||
3
PipelineAgent/ChangesChecker/Models/ChatRequest.cs
Normal file
3
PipelineAgent/ChangesChecker/Models/ChatRequest.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace PipelineAgent.ChangesChecker.Models;
|
||||
|
||||
public record ChatRequest(string FileName, string FileContent);
|
||||
@@ -9,8 +9,18 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Gitea.Net.API" Version="25.8.18" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0-preview.2.25163.2" />
|
||||
<PackageReference Include="OpenAI" Version="2.8.0" />
|
||||
<PackageReference Include="Services" Version="2.0.0-alpha.0" />
|
||||
<PackageReference Include="Services" Version="2.0.0-alpha.0.134" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Prompts\TestsChecker.txt">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Prompts\ChangesChecker.txt">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,35 +1,38 @@
|
||||
using System.Text.Json;
|
||||
using System.Xml.Linq;
|
||||
using OpenAI;
|
||||
using OpenAI.Chat;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using PipelineAgent.ChangesChecker;
|
||||
using PipelineAgent.TestsChecker;
|
||||
using Services.Gitea;
|
||||
using Services.OpenAI;
|
||||
using Services.Vault;
|
||||
|
||||
IHost builder = Host.CreateDefaultBuilder(args)
|
||||
.ConfigureServices((_, services) =>
|
||||
{
|
||||
var vaultUrl = Environment.GetEnvironmentVariable("VAULT_URL") ?? "http://vault:8200";
|
||||
var vaultToken = Environment.GetEnvironmentVariable("VAULT_TOKEN") ?? "dev-only-token";
|
||||
var vaultService = new VaultService(vaultUrl, vaultToken);
|
||||
|
||||
services.AddSingleton<IVaultService>(vaultService);
|
||||
|
||||
services.AddScoped<IGiteaService, GiteaService>();
|
||||
services.AddScoped<IOpenAiService, OpenAiService>();
|
||||
services.AddScoped<ITestsCheckerAgent, TestsCheckerAgent>();
|
||||
services.AddScoped<IChangesCheckerAgent, ChangesCheckerAgent>();
|
||||
}).Build();
|
||||
|
||||
if (args.Length == 0)
|
||||
{
|
||||
Console.WriteLine("Usage: CiAgent <command> [options]");
|
||||
Console.WriteLine("Commands: test-gate, container-gate, deploy-gate");
|
||||
Console.WriteLine("Commands: test-gate, check-changes, deploy-gate");
|
||||
return 1;
|
||||
}
|
||||
|
||||
var command = args[0];
|
||||
var commandArgs = args.Skip(1).ToArray();
|
||||
|
||||
return command switch
|
||||
return args[0] switch
|
||||
{
|
||||
"test-gate" => await RunTestGateAsync(commandArgs[0]),
|
||||
"check-changes" => await RunChangesGateAsync(),
|
||||
"test-gate" => await builder.Services.GetRequiredService<ITestsCheckerAgent>()
|
||||
.CheckTestsAsync(args.ElementAtOrDefault(1) ?? string.Empty),
|
||||
"check-changes" => await builder.Services.GetRequiredService<IChangesCheckerAgent>()
|
||||
.CheckChangesAsync(args.ElementAtOrDefault(1) ?? string.Empty),
|
||||
_ => 1
|
||||
};
|
||||
|
||||
static async Task<int> RunTestGateAsync(string arg)
|
||||
{
|
||||
var testsCheckerAgent = new TestsCheckerAgent();
|
||||
return await testsCheckerAgent.CheckTestsAsync(arg);
|
||||
}
|
||||
|
||||
static async Task<int> RunChangesGateAsync()
|
||||
{
|
||||
var changesCheckerAgent = new ChangesCheckerAgent();
|
||||
return await changesCheckerAgent.CheckChangesAsync();
|
||||
}
|
||||
38
PipelineAgent/Prompts/ChangesChecker.txt
Normal file
38
PipelineAgent/Prompts/ChangesChecker.txt
Normal file
@@ -0,0 +1,38 @@
|
||||
Jesteś Senior .NET Developerem z kilkunastoletnim stażem.
|
||||
Programujesz w .NET 8 i C# 12, znasz najlepsze praktyki programistyczne oraz najnowoczesniejsza konwencje.
|
||||
Na podstawie listy zawartości plików zmienionych w ostatnich commitach:
|
||||
|
||||
- sprwadź kod pod kątem poprawności i potencjalnych błędów
|
||||
- zasugeruj poprawki lub ulepszenia, jeśli to konieczne, tak, żeby kod był jak najbardziej optymalny i profesjonalny
|
||||
- zwróć JEDEN obiekt JSON w formacie:
|
||||
{
|
||||
decision: "approve" | "changes-requested",
|
||||
changes:
|
||||
{
|
||||
branch = "master",
|
||||
new_branch = "code-review_<losowy_ciag_znakow>",
|
||||
message = "LLM: Code review suggestions plus to co zmieniono wypisane w liscie (*) po angielsku",
|
||||
files = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
operation = "update",
|
||||
path = nazwa pliku z requesta,
|
||||
content = poprawiony plik
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
- tworz changes tylko wtedy jesli sugerujesz jakies zmiany w kodzie
|
||||
- sprawdzaj, czy sugerowane zmiany nie wprowadzają nowych błędów lub problemów
|
||||
- upewnij się, że sugerowane zmiany są zgodne z najlepszymi praktykami programistycznymi
|
||||
- jesli klasa dziedziczy po interfejsie lub klasie bazowej, nie zmieniaj nazw metod lub ich sygnatur
|
||||
- dostarczaj jasne i zwięzłe uzasadnienie dla każdej sugerowanej zmiany
|
||||
- unikaj sugerowania zmian, które są zbędne lub nieistotne
|
||||
- pamiętaj, że Twoim celem jest poprawa jakości kodu
|
||||
- sprawdzaj wygenerowany json pod kątem poprawności składniowej i kompletności przed zwróceniem go użytkownikowi
|
||||
- jesli nie sugerujesz żadnych zmian w kodzie, pole "changes" powinno być puste (null)
|
||||
Decyzja:
|
||||
- "approve" gdy kod jest w porzadku i nie potrzebuje zadnych zmian (pole changes jest null).
|
||||
- "changes-requested" gdy kod wymaga poprawek lub ulepszeń.
|
||||
Nie dodawaj żadnego tekstu poza JSON.
|
||||
13
PipelineAgent/Prompts/TestsChecker.txt
Normal file
13
PipelineAgent/Prompts/TestsChecker.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
Jesteś asystentem DevOps oceniającym wyniki testów .NET.
|
||||
Na podstawie listy błędów:
|
||||
- sklasyfikuj każdy błąd jako: CriticalBug, InfraOrConfig, FlakyTest
|
||||
- zwróć JEDEN obiekt JSON w formacie:
|
||||
{
|
||||
"decision": "block" | "allow" | "allow-with-warning",
|
||||
"reason": "krótkie wyjaśnienie po polsku"
|
||||
}
|
||||
Decyzja:
|
||||
- "block" gdy jest choć jeden CriticalBug.
|
||||
- "allow-with-warning" gdy są tylko InfraOrConfig lub FlakyTest, ale wygląda to na coś, co trzeba sprawdzić.
|
||||
- "allow" gdy wszystko wskazuje na drobne lub znane flaky testy.
|
||||
Nie dodawaj żadnego tekstu poza JSON.
|
||||
6
PipelineAgent/TestsChecker/ITestsCheckerAgent.cs
Normal file
6
PipelineAgent/TestsChecker/ITestsCheckerAgent.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace PipelineAgent.TestsChecker;
|
||||
|
||||
public interface ITestsCheckerAgent
|
||||
{
|
||||
Task<int> CheckTestsAsync(string trxPath);
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
using System.Text.Json;
|
||||
using System.Xml.Linq;
|
||||
using OpenAI;
|
||||
using OpenAI.Chat;
|
||||
using Services.OpenAI;
|
||||
|
||||
namespace PipelineAgent.TestsChecker;
|
||||
|
||||
public class TestsCheckerAgent
|
||||
public class TestsCheckerAgent(IOpenAiService openAiService) : ITestsCheckerAgent
|
||||
{
|
||||
public async Task<int> CheckTestsAsync(string trxPath)
|
||||
{
|
||||
@@ -18,19 +16,11 @@ public class TestsCheckerAgent
|
||||
try
|
||||
{
|
||||
XDocument doc = XDocument.Load(trxPath);
|
||||
|
||||
// Standardowy namespace MSTest/TRX
|
||||
XNamespace ns = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010";
|
||||
|
||||
var failed = doc
|
||||
.Descendants(ns + "UnitTestResult")
|
||||
.Where(x => (string?)x.Attribute("outcome") == "Failed")
|
||||
var failed = doc.Descendants(ns + "UnitTestResult").Where(x => (string?)x.Attribute("outcome") == "Failed")
|
||||
.ToList();
|
||||
|
||||
var total = doc
|
||||
.Descendants(ns + "UnitTestResult")
|
||||
.Count();
|
||||
|
||||
var total = doc.Descendants(ns + "UnitTestResult").Count();
|
||||
var failuresForModel = failed.Select(f => new
|
||||
{
|
||||
TestName = (string?)f.Attribute("testName") ?? "<no name>",
|
||||
@@ -41,48 +31,21 @@ public class TestsCheckerAgent
|
||||
.Value
|
||||
}).ToList();
|
||||
|
||||
var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY");
|
||||
if (string.IsNullOrWhiteSpace(apiKey))
|
||||
var prompt = await File.ReadAllTextAsync(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Prompts",
|
||||
"TestsChecker.txt"));
|
||||
|
||||
var decisionDoc = await openAiService.GetResponseFromChat(failuresForModel, prompt);
|
||||
|
||||
if (decisionDoc == null)
|
||||
{
|
||||
Console.WriteLine("AI‑Gate: OPENAI_API_KEY not set, blocking by default.");
|
||||
Console.WriteLine("AI‑Gate: no response from LLM, blocking by default.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
var client = new OpenAIClient(apiKey);
|
||||
|
||||
var systemPrompt = """
|
||||
Jesteś asystentem DevOps oceniającym wyniki testów .NET.
|
||||
Na podstawie listy błędów:
|
||||
|
||||
- sklasyfikuj każdy błąd jako: CriticalBug, InfraOrConfig, FlakyTest
|
||||
- zwróć JEDEN obiekt JSON w formacie:
|
||||
{
|
||||
"decision": "block" | "allow" | "allow-with-warning",
|
||||
"reason": "krótkie wyjaśnienie po polsku"
|
||||
}
|
||||
|
||||
Decyzja:
|
||||
- "block" gdy jest choć jeden CriticalBug.
|
||||
- "allow-with-warning" gdy są tylko InfraOrConfig lub FlakyTest, ale wygląda to na coś, co trzeba sprawdzić.
|
||||
- "allow" gdy wszystko wskazuje na drobne lub znane flaky testy.
|
||||
Nie dodawaj żadnego tekstu poza JSON.
|
||||
""";
|
||||
|
||||
var userContent = JsonSerializer.Serialize(failuresForModel);
|
||||
|
||||
var chat = client.GetChatClient("gpt-4.1-mini");
|
||||
|
||||
var response = await chat.CompleteChatAsync(new SystemChatMessage(systemPrompt),
|
||||
new UserChatMessage($"Błędy testów:\n{userContent}"));
|
||||
|
||||
var json = response.Value.Content[0].Text;
|
||||
Console.WriteLine("AI‑Gate LLM raw response:");
|
||||
Console.WriteLine(json);
|
||||
|
||||
var decisionDoc = JsonDocument.Parse(json);
|
||||
var decision = decisionDoc.RootElement.GetProperty("decision").GetString();
|
||||
var reason = decisionDoc.RootElement.GetProperty("reason").GetString();
|
||||
|
||||
Console.WriteLine($"Total: {total}, Failed: {failed.Count}");
|
||||
Console.WriteLine($"AI‑Gate decision: {decision}");
|
||||
Console.WriteLine($"Reason: {reason}");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user