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" />
|
||||
</Folder>
|
||||
<Folder Name="/tools/">
|
||||
<Project Path="tools/ImportAssist.Analyze/ImportAssist.Analyze.csproj" />
|
||||
<Project Path="tools/ImportAssist.Upload/ImportAssist.Upload.csproj" />
|
||||
</Folder>
|
||||
</Solution>
|
||||
@@ -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.
|
||||
@@ -135,4 +135,43 @@ Dateistrukturfehler in Zeile 3.
|
||||
### Bewertung
|
||||
Teilweise bestanden.
|
||||
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