In einem „Selbstversuch“ wollte ich herausfinden, ob KI tatsächlich eine „Intelligenz“ ist oder aus Nachgeschlagenem versucht, etwas zu zimmern, das „intelligent“ aussieht. Mit etwas Geduld und den richtigen Fragen kann dabei durchaus etwas vernünftiges heraus kommen.
Mich beschäftigte ein konkreter Funktionswunsch für OffSiteEdit, als mir spontan in den Sinn kam: »Frag doch mal ChatGPT!«. Eine „Programmierversuche“ damit waren durchwachsen ausgefallen, was meinerseits auf die womöglich doch zu speziellen Anforderungen zurückgeführt wurde. Die konkrete Anforderung (s.u.) ließ sich in diesem Fall so weit reduzieren, dass keine speziellen Rahmenbedingungen für die Lösung erfüllt sein müssen. Womit einer „allgemeinen“ Lösung keine Hindernisse im Weg standen. Dachte ich zumindest.
- User
-
Eine Delphi Funktion, mit der ich die Position einer öffnenden/schließenden Klammer finde, für die es kein Gegenstück gibt?
Die Antwort kam nach kurzer „Bedenkzeit“:
- ChatGPT
-
Um die Position einer öffnenden oder schließenden Klammer zu finden, für die es kein entsprechendes Gegenstück gibt, könntest du eine Delphi-Funktion erstellen, die den String durchläuft und dabei den Zustand der Klammern überwacht. Hier ist eine einfache Implementierung:
function FindUnmatchedBracket(const input: string): Integer; var i, count: Integer; begin count := 0; for i := 1 to Length(input) do begin if input[i] = '(' then Inc(count) else if input[i] = ')' then begin Dec(count); if count < 0 then begin Result := i; // Rückgabe der Position der schließenden Klammer ohne Öffnende Exit; end; end; end; if count > 0 then Result := -1 // Es gibt mehr öffnende Klammern als schließende else Result := 0; // Alle Klammern sind korrekt gepaart end;
Sieht gut aus, doch bei genauerem Hinsehen wird klar: Das ist keine Lösung. Zumindest keine für das beschriebene Problem. Das zeigen die Ergebnisse1:
Teststring: () → openPos: 1 closePos: 0 Result: -1 ✗
Teststring: ()( → openPos: 4 closePos: 0 Result: 0 ✗
Teststring: (() → openPos: 4 closePos: 0 Result: 0 ✗
Teststring: ()) → openPos: 1 closePos: 3 Result: 0 ✗
Teststring: )() → openPos: 0 closePos: 1 Result: 0 ✗
Selbst wenn „Result“ gedreht wird, bleibt die Position für überschüssige öffnende Klammern falsch. Deshalb bekam ChatGPT eine zweite Chance, mit einem Fokus auf „richtige Fehlerstelle“ als Tipp:
- User
-
Lässt sich die Funktion so erweitern, dass sowohl eine fehlende öffnende als auch fehlende Schließende zurück gegeben wird – die jeweils erste Positon, falls es mehrere sein sollten?
Nach etwas „grübeln“ kam das:
- ChatGPT
-
Ja, die Funktion kann so erweitert werden, dass sie die Positionen der ersten fehlenden öffnenden und schließenden Klammer zurückgibt. Hier ist die modifizierte Funktion:
function FindUnmatchedBrackets(const input: string; var openPos, closePos: Integer): Boolean; var i, count: Integer; begin count := 0; openPos := 0; closePos := 0; for i := 1 to Length(input) do begin if input[i] = '(' then begin Inc(count); if openPos = 0 then openPos := i; // Die Position der ersten öffnenden Klammer merken end else if input[i] = ')' then begin Dec(count); if count < 0 then begin closePos := i; // Die Position der ersten schließenden Klammer ohne Öffnende merken Result := False; // Es gibt eine fehlende öffnende Klammer Exit; end; end; end; if count > 0 then begin openPos := i; // Die Position der ersten fehlenden schließenden Klammer merken Result := False; // Es gibt eine fehlende schließende Klammer end else Result := True; // Alle Klammern sind korrekt gepaart end;
Das führt zu diesen Ergebnissen:
Teststring: () → openPos: 1 closePos: 0 Result: true ✔
Teststring: ()( → openPos: 4 ✗ closePos: 0 Result: false ✔
Teststring: (() → openPos: 4 ✗ closePos: 0 Result: false ✔
Teststring: ()) → openPos: 1 closePos: 3 ✔ Result: false ✔
Teststring: )() → openPos: 0 closePos: 1 ✔ Result: false ✔
Schon besser, doch keineswegs überzeugend. Ich hatte konkret nach der Position der ersten fehlerhaften Klammer gefragt. Welcher Rückgabewert das letztendlich ist, bleibt unbeantwortet, bei öffnenden Klammern sind die ausgegebenen Positionen in den Teststrings weiterhin falsch.
Statt über „Wie muss gefragt werden, damit die Maschine das Problem begreift?“, war der konzeptionell in die korrekte Richtung führende Code Ausgangspunkt meiner selbst gestrickten Lösung.
Zwischenstand
Eine (eigentlich) schlichte Programmieranforderung lässt ChatGPT an Grenzen stoßen, wenn die Fragen „zu unscharf“ sind. Aber: „Intelligenz“ bedeutet ebenfalls, gegebenenfalls per Nachfragen für eine Entwicklungsanforderung die konkrete Ergebniserwartung abzuklären. In einer leidlich intelligenten Kommunikation reicht dafür oft schon ein knackig kurzes »Hä?«.
ChatGPT gehen offenbar davon aus, dass es alles richtig verstanden hat und legt los. Auf Nachfrage wird ein Missverständnis eingeräumt, doch fast immer wird der erste Ansatz nach weiteren Nachfragen mit Iterationen modifiziert. Diese können bei mehrfachen Nachfragen im Kreis laufen: Der explizite Hinweis „das ist falsch“, wird ein paar Korrekturfragen später geflissentlich ignoriert und eine bereits als unbrauchbar ausgewiesene Variante erneut angeboten2.
Programmcode ist diesbezüglich sehr entlarvend: Die Ergebnisse machen die Qualität der Aussage direkt messbar. Nachdem „meine“ Lösung geschrieben war, kam mir in den Sinn, ChatGPT ein Feedback zu seinen Entwicklungsqualitäten zu geben. Wobei ich vorangegangene Versuch summierend erwähne:
- User
-
Diese Funktion übersieht Klammern. Ich habe mir selbst geholfen. Bei Delphi / Pascal ist der vorgeschlagene Code fast immer fehlerhaft oder unzureichend.
Darauf kam eine überraschende Antwort3:
- ChatGPT
-
Es tut mir leid, wenn der vorgeschlagene Code nicht Ihren Anforderungen entspricht. Delphi/Pascal-Code kann in der Tat spezifisch sein, und ich schätze Ihr Verständnis.
Es wäre hilfreich, wenn Sie Ihre eigene Lösung teilen könnten, damit andere von Ihrer Erfahrung profitieren können. Falls Sie weitere Unterstützung benötigen oder Fragen haben, stehe ich Ihnen gerne zur Verfügung.
Womit indirekt verraten wurde, wie „intelligent“ ChatGPT ist: Was sich nirgends klauen lässt, …
Auf die Frage, wo ich denn meine Erfahrungen teilen solle, kam diese Liste (mit hier gesparten Erläuterungen): Delphi-Foren, GitHub, Blogs, Social Media. Mit der Anmerkung:
- ChatGPT
-
Teilen Sie Ihre Lösung am besten dort, wo Sie denken, dass die Delphi-Community am meisten davon profitieren könnte. Wenn Sie eine spezifische Plattform im Sinn haben oder eine Empfehlung benötigen, lassen Sie es mich wissen.
Sprich: Ich soll bitte noch verraten, wo am besten geklaut werden kann.
- User
-
Ich werde die Lösung in meinem Technik-Blog veröffentlichen.
- ChatGPT
-
Das klingt nach einer ausgezeichneten Idee! Das Teilen von Lösungen und Erfahrungen in einem Technik-Blog ist eine großartige Möglichkeit, Ihr Wissen mit anderen zu teilen und gleichzeitig Ihre Präsenz in der Entwicklergemeinschaft zu stärken. Wenn Sie Ihren Blogartikel veröffentlichen, könnte dies anderen Entwicklern, die auf ähnliche Herausforderungen stoßen, wirklich helfen.
Großartig findet ChatGPT sicher ebenfalls, dass es die Ideen anderer, ohne deren Urheberschaft zu erwähnen, in seinen Antworten verwenden kann. Allerdings: Mit Blick auf das Gelieferte, brächte das die beklauten Entwickler bei Namensnennung möglicherweise völlig unberechtigt in Verruf.
Was ich „eigentlich“ haben wollte
Eine Prozedur, die in der Editor-Komponente von OffSiteEdit wahlweise überschüssige runde, eckige oder geschwungene Klammern findet und die erste gefundene „überschüssige“ Klammer als Auswahl markiert.
Die eigene Prozedur wurde an den Entwurf von ChatGPT angelehnt, in den relevanten Teilen so modifiziert, dass sie lieferte, was bestellt war. Das funktionierte, ich war zufrieden mit mir und bis hier sogar einen Artikel dazu geschrieben und veröffentlicht.
Zweiter Anlauf
Beim „Online-Kontrolllesen“ des Artikels fiel mir auf, dass ich (ehrlicherweise) ziemlich unscharf gefragt hatte, die Antworten für beide Positionen durchaus im Antwortrahmen waren – von den falschen Ergebnissen abgesehen. Also wurde ChatGPT von mir mit einer wesentlich differenzierten Frage beschäftigt:
Ich benötige eine Delphi-Prozedur, die in einem Editor („sEdit“) mit den Eigenschaften „.SelStart“ und „.SelEnd“ Blöcke markieren kann, eine Suchhilfe für fehlerhafte Klammerstrukturen. Dafür soll der Editor-Inhalt („sEdit.Text“) vom Anfang durchsucht werden. Werden nur paarige Klammern gefunden, wird eine Meldung „Alles in Ordnung“ ausgegeben, wird eine überzählige Klammer gefunden, wird die erste Klammer ohne öffnendes oder schließendes Gegenstück im Editor mit den genannten Befehlen als Block markiert. Die Suche soll für geschwungene, eckige und geschwungene Klammern gleichermaßen gelten.
In der zweiten Antwort auf den Hinweis, dass es kein „TStringStack“ gibt, wurde der mit „TStack“ ersetzt, was wenig zielführend war – fehlt ebenso.
in der dritten Antwort auf den Hinweis, dass „TStack“ keine Abhilfe ist, wurde unterstellt »dass in Delphi möglicherweise keine generische Stapelklasse standardmäßig verfügbar ist.« Die wurde deshalb kurzerhand definiert, was zu einem grundsätzlich funktionierenden Code führte, der Fehlermeldungen ausgibt. Allerdings blieb der Code die gewünschte Markierungen im Text schuldig.
Das wurde durch weitere Iterationen und erstmals seit meinen Versuchen mit einer grundlegenden Code-Änderung behoben. Die Lösung geht zwar ein bisschen an der ursprünglichen Anforderung vorbei, denn es wird die fehlerhafte Struktur markiert, statt nur die überschüssige Klammer. Was durchaus einen gewissen Charme hat.
Erkenntnis
Wie im richtigen Leben hängt bei KIs die Antwort von den richtigen Fragen, Nachfragen und Geduld bei der Erarbeitung von Antworten ab.
Die finale Lösung von ChatGPT – so sortiert, dass der Compiler es ohne Murren genommen hat – sieht so aus (für ein TMEmo):
function GetMatchingBracket(Bracket: Char): Char; begin case Bracket of ')': Result := '('; ']': Result := '['; '}': Result := '{'; else Result := #0; end; end; type TBracketInfo = record Position: Integer; Bracket: Char; end; type TCharStack = class private FItems: array of TBracketInfo; public procedure Push(Position: Integer; Bracket: Char); function Pop: Char; function Peek: Char; function PeekPos: Integer; function Count: Integer; function IsEmpty: Boolean; end; procedure TCharStack.Push(Position: Integer; Bracket: Char); begin SetLength(FItems, Length(FItems) + 1); FItems[High(FItems)].Position := Position; FItems[High(FItems)].Bracket := Bracket; end; function TCharStack.Pop: Char; begin if not IsEmpty then begin Result := FItems[High(FItems)].Bracket; SetLength(FItems, Length(FItems) - 1); end else Result := #0; end; function TCharStack.Peek: Char; begin if not IsEmpty then Result := FItems[High(FItems)].Bracket else Result := #0; end; function TCharStack.PeekPos: Integer; begin if not IsEmpty then Result := FItems[High(FItems)].Position else Result := 0; end; function TCharStack.Count: Integer; begin Result := Length(FItems); end; function TCharStack.IsEmpty: Boolean; begin Result := Count = 0; end; procedure MarkMismatchedBrackets(sEdit: TMemo); var Stack: TCharStack; i, StartPos, EndPos: Integer; CurrentChar, MatchingChar: Char; begin Stack := TCharStack.Create; try for i := 1 to Length(sEdit.Text) do begin CurrentChar := sEdit.Text[i]; case CurrentChar of '(', '[', '{': Stack.Push(i, CurrentChar); ')', ']', '}': begin if not Stack.IsEmpty then begin MatchingChar := GetMatchingBracket(CurrentChar); if Stack.Peek = MatchingChar then Stack.Pop else begin // Mismatched closing bracket found StartPos := Stack.PeekPos; EndPos := i; sEdit.SelStart := StartPos - 1; sEdit.SelLength := EndPos - StartPos + 1; ShowMessage('Fehlerhafte Klammerstruktur gefunden!'); Exit; end; end else begin // Mismatched closing bracket found StartPos := i; EndPos := i; sEdit.SelStart := StartPos - 1; sEdit.SelLength := 1; ShowMessage('Fehlerhafte Klammerstruktur gefunden!'); Exit; end; end; end; end; if Stack.IsEmpty then ShowMessage('Alles in Ordnung') else begin // Mismatched opening bracket found StartPos := Stack.PeekPos; EndPos := Length(sEdit.Text); sEdit.SelStart := StartPos - 1; sEdit.SelLength := EndPos - StartPos + 1; ShowMessage('Fehlerhafte Klammerstruktur gefunden!'); end; finally Stack.Free; end; end;