KI-Backend: Umfangs-Umschalter + Prompt Caching (4.5.15/4.5.16)

Umfangs-Umschalter: Checkbox "Auch bestehende Stundeninhalte anpassen" im
AiAssistDialog steuert, ob die KI bestehende Stunden inhaltlich ändern darf oder
die Einheit nur um neue Stunden erweitern soll. Zweifach durchgesetzt (Systemprompt
+ hartes client-seitiges Verwerfen in ApplyResponse), nicht nur der KI-Antwort
vertraut. Dabei auch einen Bug gefixt: eine bereits "Durchgeführt" markierte Stunde
wurde durch eine übernommene KI-Änderung stillschweigend auf "Geplant" zurückgesetzt.

Prompt Caching: der Systemprompt ist jetzt vollständig statisch (Voraussetzung für
Caching) und wird von AnthropicProvider.php als "cache_control: ephemeral" markiert
— wiederholte Anfragen innerhalb der 5-Minuten-TTL zahlen nur den reduzierten
Cache-Read-Preis. transactions-Tabelle und Preistabelle um Cache-Token-Spalten
erweitert, Migration für bereits deployte Installationen beigelegt.
This commit is contained in:
2026-08-16 16:06:52 +02:00
parent 4437f951d9
commit 1928916eac
14 changed files with 328 additions and 38 deletions
@@ -125,6 +125,52 @@ public sealed class AiPlanningServiceTests
Assert.Equal(group.Id, lesson.GroupId);
}
/// Die KI kennt/liefert keinen Status — ohne diese Absicherung würde eine bereits gehaltene
/// Stunde durch eine übernommene KI-Änderung stillschweigend auf "Geplant" zurückgesetzt.
[Fact]
public void ApplyResponse_BekannteId_BehaeltStatusDerBestehendenLessonBei()
{
var group = new LearningGroup();
var unit = new Unit { GroupId = group.Id, Title = "T" };
var existing = new Lesson { UnitId = unit.Id, GroupId = group.Id, Topic = "Alt", Status = LessonStatus.Conducted };
var lessons = new FakeLessons();
lessons.Add(existing);
var service = Build(lessons, new FakeGroups([group]), new FakeSubjects([]),
new FakeCompetencyDomains(), new FakeAlternativeLessonPaths([]));
var accepted = new List<AiLesson> { new() { Id = existing.Id, Topic = "Nachträglich präzisiert" } };
var result = service.ApplyResponse(unit, accepted);
Assert.Equal(LessonStatus.Conducted, Assert.Single(result).Status);
}
/// Umfangs-Umschalter (Nutzer-Nachtrag): "Einheit umplanen ohne Stunden zu ändern" muss auch
/// dann greifen, wenn die KI die Anweisung im Systemprompt ignoriert und trotzdem eine
/// bestehende Id zurückgibt — client-seitig hart durchgesetzt, nicht nur per Prompt erbeten.
[Fact]
public void ApplyResponse_AenderungVerboten_VerwirftUpdateBestehenderLesson()
{
var group = new LearningGroup();
var unit = new Unit { GroupId = group.Id, Title = "T" };
var existing = new Lesson { UnitId = unit.Id, GroupId = group.Id, Topic = "Alt" };
var lessons = new FakeLessons();
lessons.Add(existing);
var service = Build(lessons, new FakeGroups([group]), new FakeSubjects([]),
new FakeCompetencyDomains(), new FakeAlternativeLessonPaths([]));
var accepted = new List<AiLesson>
{
new() { Id = existing.Id, Topic = "Sollte verworfen werden" },
new() { Id = null, Topic = "Neue Stunde bleibt erlaubt" },
};
var result = service.ApplyResponse(unit, accepted, allowModifyingExistingLessons: false);
var lesson = Assert.Single(result);
Assert.Equal("Neue Stunde bleibt erlaubt", lesson.Topic);
}
[Fact]
public void ApplyResponse_NullId_WirdAlsNeueLessonBehandelt()
{