Fix: parseJson chat responses, force override for conflicts

- parseJson: plain text (kein { oder [) → type=chat statt type=unknown
  Behebt WARNING für normale Chat-Antworten wie "Alles klar! [END]"
- AgentAIService Prompt: Regel 12 — Konflikt-Handling mit force:true erklärt
  AI fragt nach Überschneidung und sendet force:true wenn User bestätigt
- AgentActionService: Duplikat-Check bei force=true überspringen
  damit erzwungene Events auch bei gleichem Titel+Datum gespeichert werden

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
main
boban 2026-04-19 08:22:52 +02:00
parent 7d50647f39
commit 89cb058e83
2 changed files with 14 additions and 2 deletions

View File

@ -549,6 +549,8 @@ REGELN:
9. Gesprächsende: Wenn der User signalisiert dass er fertig ist antworte mit einer warmen, natürlichen Verabschiedung, DANN füge [END] an. Beispiele: "Super, dann bis später! Meld dich einfach wenn du was brauchst. [END]", "Alles klar, schönen Tag noch! [END]", "Passt, viel Spaß heute! [END]". NICHT: "Okay, [END]" oder nur "Bis dann, [END]" 9. Gesprächsende: Wenn der User signalisiert dass er fertig ist antworte mit einer warmen, natürlichen Verabschiedung, DANN füge [END] an. Beispiele: "Super, dann bis später! Meld dich einfach wenn du was brauchst. [END]", "Alles klar, schönen Tag noch! [END]", "Passt, viel Spaß heute! [END]". NICHT: "Okay, [END]" oder nur "Bis dann, [END]"
10. Unsicher nachfragen. Nichts erstellen ohne klare Absicht. 10. Unsicher nachfragen. Nichts erstellen ohne klare Absicht.
11. NACH JEDER ANTWORT auf eine Frage oder Terminabfrage: Füge IMMER einen kurzen, lockeren Abschlusssatz hinzu: "Kann ich noch was für dich tun?", "Sonst noch was?", "Brauchst du noch was?". Ausnahme: Wenn der User fertig ist [END] verwenden (Regel 9). Jede Antwort endet entweder mit einer Folgefrage ODER mit [END]. 11. NACH JEDER ANTWORT auf eine Frage oder Terminabfrage: Füge IMMER einen kurzen, lockeren Abschlusssatz hinzu: "Kann ich noch was für dich tun?", "Sonst noch was?", "Brauchst du noch was?". Ausnahme: Wenn der User fertig ist [END] verwenden (Regel 9). Jede Antwort endet entweder mit einer Folgefrage ODER mit [END].
12. KONFLIKT-HANDLING: Wenn das Backend meldet dass ein Termin sich mit einem anderen überschneidet, frage den User: "Der Termin überschneidet sich mit [Termintitel]. Soll ich ihn trotzdem eintragen?" Wenn der User ja/trotzdem/egal/bitte/genau sagt sende dasselbe Event nochmal MIT "force": true im data-Objekt:
{"type": "event", "data": {"title": "Volleyball", "datetime": "2026-04-19 14:00", "force": true}}
Datum: TT.MM(.JJJJ), "heute"=heute, "morgen"=+1. Kein Datum→heute. Titel: max 5-7 Wörter, kein Datum. Datum: TT.MM(.JJJJ), "heute"=heute, "morgen"=+1. Kein Datum→heute. Titel: max 5-7 Wörter, kein Datum.
WICHTIG bei Terminabfragen: WICHTIG bei Terminabfragen:
@ -686,6 +688,14 @@ PROMPT;
$json = json_decode($cleaned, true); $json = json_decode($cleaned, true);
if ($json === null) { if ($json === null) {
// Kein gültiges JSON — wenn kein { oder [ am Anfang → plain-text Chat-Antwort
$isJson = str_starts_with($cleaned, '{') || str_starts_with($cleaned, '[');
if (!$isJson) {
return [
'type' => 'chat',
'data' => ['message' => $cleaned],
];
}
return self::fallback(); return self::fallback();
} }

View File

@ -50,7 +50,8 @@ class AgentActionService
if (!empty($data['start']) && !empty($data['end'])) { if (!empty($data['start']) && !empty($data['end'])) {
$title = $data['title'] ?? 'Termin'; $title = $data['title'] ?? 'Termin';
$dateStr = Carbon::parse($data['start'])->toDateString(); $dateStr = Carbon::parse($data['start'])->toDateString();
if (Event::where('user_id', $user->id)->where('title', $title)->whereDate('starts_at', $dateStr)->exists()) { $force = !empty($data['force']);
if (!$force && Event::where('user_id', $user->id)->where('title', $title)->whereDate('starts_at', $dateStr)->exists()) {
return ['status' => 'failed', 'message' => "Termin \"{$title}\" existiert an diesem Tag bereits.", 'meta' => []]; return ['status' => 'failed', 'message' => "Termin \"{$title}\" existiert an diesem Tag bereits.", 'meta' => []];
} }
@ -74,7 +75,8 @@ class AgentActionService
if (!empty($data['datetime'])) { if (!empty($data['datetime'])) {
$title = $data['title'] ?? 'Termin'; $title = $data['title'] ?? 'Termin';
$dateStr = Carbon::parse($data['datetime'])->toDateString(); $dateStr = Carbon::parse($data['datetime'])->toDateString();
if (Event::where('user_id', $user->id)->where('title', $title)->whereDate('starts_at', $dateStr)->exists()) { $force = !empty($data['force']);
if (!$force && Event::where('user_id', $user->id)->where('title', $title)->whereDate('starts_at', $dateStr)->exists()) {
return ['status' => 'failed', 'message' => "Termin \"{$title}\" existiert an diesem Tag bereits.", 'meta' => []]; return ['status' => 'failed', 'message' => "Termin \"{$title}\" existiert an diesem Tag bereits.", 'meta' => []];
} }