Add C# Azure AI analysis tool and document evaluation runs

This commit is contained in:
admin committed 2026-09-10 11:24:45 +02:00
1 parent 1d9011faa8
commit 12bc2a1822
5 files changed
+235

No files matched your search

+1
View File
@@ -10,6 +10,7 @@
<Project Path="tests/ImportAssist.Core.Tests/ImportAssist.Core.Tests.csproj" />
</Folder>
<Folder Name="/tools/">
<Project Path="tools/ImportAssist.Analyze/ImportAssist.Analyze.csproj" />
<Project Path="tools/ImportAssist.Upload/ImportAssist.Upload.csproj" />
</Folder>
</Solution>
+48
View File
@@ -96,3 +96,51 @@ Azurite speichert seine Daten im angegebenen lokalen Verzeichnis.
Lokaler Prototyp mit synthetischen Daten.
Cloud-Deployment und KI-Anbindung stehen noch aus.
## KI-Analyse aus C#
`ImportAssist.Analyze` liest Analyseprompt, Schnittstellenbeschreibung
und einen gespeicherten JSON-Prüfbericht ein und ruft Azure OpenAI auf.
### Lokale Konfiguration
User Secrets für das Projekt initialisieren:
```bash
dotnet user-secrets init --project tools/ImportAssist.Analyze
```
Folgende Konfigurationswerte im zugehörigen User-Secrets-Speicher setzen:
- AzureOpenAI:Endpoint – Azure-OpenAI-Basisadresse der Ressource
- AzureOpenAI:Deployment – importassist-analysis
- AzureOpenAI:ApiKey – Schlüssel der Ressource
API-Schlüssel ausschließlich lokal hinterlegen.
User Secrets liegen außerhalb des Repositorys, sind aber unverschlüsselt.
### Ausführen
Alle Befehle aus dem Repository-Hauptverzeichnis ausführen:
```bash
dotnet run --project tools/ImportAssist.Analyze -- \
samples/invoices-report.json
dotnet run --no-build --project tools/ImportAssist.Analyze -- \
samples/invoices-rejected-report.json
```
Azurite und der Functions-Host werden für diese gespeicherten Berichte
nicht benötigt. Der Modellaufruf benötigt eine Internetverbindung.
Die Analyse erscheint auf stdout; Statusmeldungen und Tokenverbrauch
erscheinen auf stderr.
Exitcodes:
- 0: Modellantwort regulär abgeschlossen; keine fachliche Qualitätsgarantie.
- 1: Konfigurations-, Datei- oder Aufruffehler.
- 2: Modellantwort nicht regulär abgeschlossen.
Die Antwort ist Freitext. Eine automatische fachliche Bewertung und
ein verbindliches JSON-Ausgabeformat sind noch nicht implementiert.
+39
View File
@@ -136,3 +136,42 @@ Dateistrukturfehler in Zeile 3.
Teilweise bestanden.
Kernaussage korrekt; Korrekturvorschlag und Wortwahl benötigen
Präzisierung. Eine Quellenzuordnung bleibt offen.
## C#-Testläufe vom 10.09.2026
### Konfiguration
- Tool: ImportAssist.Analyze
- SDK: .NET 10.0.112
- Azure.AI.OpenAI: 2.1.0
- Modell: gpt-4.1-mini, Version 2025-04-14
- Bereitstellung: importassist-analysis
- Eingaben: aktueller Analyseprompt, Schnittstellenspezifikation
und jeweiliger JSON-Prüfbericht
- Maximale Ausgabetokens: 2000
- Jeder Aufruf ohne vorherigen Chatverlauf und ohne Tools
### Completed-Bericht
- Fehlerzeilen und Regelkennungen korrekt zugeordnet.
- Keine unbelegte Importfreigabe.
- Währung korrekt als ungeprüft behandelt.
- Ungenaue Formulierung: Nur eine Rechnung sei „validiert worden“.
Tatsächlich wurden fünf geprüft; eine bestand die Prüfregeln.
- Gutschrift als möglicher Prüfansatz erwähnt, nicht als Tatsache.
- Tokenverbrauch: 1434 Eingabe, 605 Ausgabe.
Bewertung: teilweise bestanden; sprachliche Unschärfe verbleibt.
### Rejected-Bericht
- Strukturfehler in Zeile 3 korrekt CSV-001 zugeordnet.
- Ausgebliebene fachliche Prüfung korrekt erklärt.
- Keine konkrete fehlende Spalte erfunden.
- Originalinhalt der betroffenen Zeile als fehlende Information benannt.
- Prüfung der Quelldaten statt unbegründeter Korrektur empfohlen.
- Tokenverbrauch: 1202 Eingabe, 426 Ausgabe.
Bewertung: für diesen Testlauf bestanden.
### Grenzen
Je ein manueller C#-Testlauf pro Fall.
Keine Aussage über zuverlässige Wiederholbarkeit oder andere Eingaben.
Die Antworten werden bislang als Freitext ausgegeben und manuell geprüft.
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UserSecretsId>b94ecd7c-a44e-42f7-960b-3a8a6238991c</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" Version="2.1.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.12" />
</ItemGroup>
</Project>
+131
View File
@@ -0,0 +1,131 @@
using System.ClientModel;
using Azure.AI.OpenAI;
using Microsoft.Extensions.Configuration;
using OpenAI.Chat;
if (args.Length != 1)
{
Console.Error.WriteLine(
"Aufruf aus dem Repository-Hauptverzeichnis: " +
"dotnet run --project tools/ImportAssist.Analyze -- <Prüfbericht.json>");
return 1;
}
var configuration = new ConfigurationBuilder()
.AddUserSecrets<Program>()
.Build();
var endpoint = configuration["AzureOpenAI:Endpoint"];
var deployment = configuration["AzureOpenAI:Deployment"];
var apiKey = configuration["AzureOpenAI:ApiKey"];
if (string.IsNullOrWhiteSpace(endpoint) ||
string.IsNullOrWhiteSpace(deployment) ||
string.IsNullOrWhiteSpace(apiKey))
{
Console.Error.WriteLine(
"Konfiguration fehlt: Endpoint, Deployment und ApiKey " +
"müssen in den User Secrets gesetzt sein.");
return 1;
}
if (!Uri.TryCreate(endpoint, UriKind.Absolute, out var endpointUri) ||
endpointUri.Scheme != Uri.UriSchemeHttps)
{
Console.Error.WriteLine("Endpoint muss eine gültige HTTPS-Adresse sein.");
return 1;
}
try
{
var instructions = await File.ReadAllTextAsync(
"docs/ai-analysis-prompt.md");
var specification = await File.ReadAllTextAsync(
"docs/invoice-interface-v1.md");
var report = await File.ReadAllTextAsync(args[0]);
if (string.IsNullOrWhiteSpace(instructions) ||
string.IsNullOrWhiteSpace(specification) ||
string.IsNullOrWhiteSpace(report))
{
Console.Error.WriteLine("Eine der Eingabedateien ist leer.");
return 1;
}
var azureClient = new AzureOpenAIClient(
endpointUri, new ApiKeyCredential(apiKey));
var chatClient = azureClient.GetChatClient(deployment);
ChatMessage[] messages =
[
new SystemChatMessage(instructions),
new UserChatMessage($"""
Schnittstellenbeschreibung:
{specification}
Prüfbericht:
{report}
Analysiere diesen Prüfbericht gemäß deinen Anweisungen
und der beigefügten Schnittstellenbeschreibung.
""")
];
var options = new ChatCompletionOptions
{
MaxOutputTokenCount = 2000
};
Console.Error.WriteLine($"Analyse mit {deployment} läuft ...");
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(90));
ChatCompletion completion = await chatClient.CompleteChatAsync(
messages, options, timeout.Token);
foreach (var part in completion.Content)
{
Console.Write(part.Text);
}
Console.WriteLine();
Console.Error.WriteLine(
$"Tokens: Eingabe {completion.Usage.InputTokenCount}, " +
$"Ausgabe {completion.Usage.OutputTokenCount}");
if (completion.FinishReason != ChatFinishReason.Stop)
{
Console.Error.WriteLine(
$"Antwort nicht regulär abgeschlossen: {completion.FinishReason}");
return 2;
}
return 0;
}
catch (ClientResultException ex)
{
Console.Error.WriteLine(
$"Azure-Modellaufruf fehlgeschlagen. HTTP-Status: {ex.Status}");
return 1;
}
catch (OperationCanceledException)
{
Console.Error.WriteLine("Die Analyse hat das Zeitlimit überschritten.");
return 1;
}
catch (IOException ex)
{
Console.Error.WriteLine($"Dateifehler: {ex.Message}");
return 1;
}
catch (UnauthorizedAccessException)
{
Console.Error.WriteLine("Keine Leseberechtigung für eine Eingabedatei.");
return 1;
}