Add C# Azure AI analysis tool and document evaluation runs
This commit is contained in:
1 parent
1d9011faa8
commit
12bc2a1822
5 files changed
+236
-1
No files matched your search
@@ -10,6 +10,7 @@
|
|||||||
<Project Path="tests/ImportAssist.Core.Tests/ImportAssist.Core.Tests.csproj" />
|
<Project Path="tests/ImportAssist.Core.Tests/ImportAssist.Core.Tests.csproj" />
|
||||||
</Folder>
|
</Folder>
|
||||||
<Folder Name="/tools/">
|
<Folder Name="/tools/">
|
||||||
|
<Project Path="tools/ImportAssist.Analyze/ImportAssist.Analyze.csproj" />
|
||||||
<Project Path="tools/ImportAssist.Upload/ImportAssist.Upload.csproj" />
|
<Project Path="tools/ImportAssist.Upload/ImportAssist.Upload.csproj" />
|
||||||
</Folder>
|
</Folder>
|
||||||
</Solution>
|
</Solution>
|
||||||
@@ -96,3 +96,51 @@ Azurite speichert seine Daten im angegebenen lokalen Verzeichnis.
|
|||||||
|
|
||||||
Lokaler Prototyp mit synthetischen Daten.
|
Lokaler Prototyp mit synthetischen Daten.
|
||||||
Cloud-Deployment und KI-Anbindung stehen noch aus.
|
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.
|
||||||
@@ -135,4 +135,43 @@ Dateistrukturfehler in Zeile 3.
|
|||||||
### Bewertung
|
### Bewertung
|
||||||
Teilweise bestanden.
|
Teilweise bestanden.
|
||||||
Kernaussage korrekt; Korrekturvorschlag und Wortwahl benötigen
|
Kernaussage korrekt; Korrekturvorschlag und Wortwahl benötigen
|
||||||
Präzisierung. Eine Quellenzuordnung bleibt offen.
|
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>
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
Reference in new issue
Block a user