10.4 Beispiel: Code automatisiert kommentieren
Viele Programmierer und Programmiererinnen konzentrieren sich lieber auf den eigentlichen Code als auf die lästige Pflicht, diesen auch noch mit Kommentaren zu versehen. Wenn dann aber jemand anderer den Code weiterentwickeln oder warten soll, ist guter Rat teuer. Da haben wir uns gedacht: Könnte nicht die KI vorhandenen Code mit Kommentaren versehen? Erste Versuche in ChatGPT sind durchweg mit guten Ergebnissen verlaufen (siehe auch Kapitel 6, »Software dokumentieren«). Daraufhin haben wir versucht, die Sache zu automatisieren. Wir haben die folgende Rollenbeschreibung verwendet:
Role: You are a coding assistant. Your goal is to add missing comments to existing code. At the very least, add a header to the file to indicate its purpose. Try to explain the purpose of each function/method.
If necessary, add comments to explain blocks of code. Don’t change/remove existing comments.
Apart from comments, leave the code as it is. The functionality of the code MUST NOT be changed.
DO NOT ADD EXPLANATIONS in your response.
DO NOT USE MARKDOWN to format your response.
Wenn Sie den Code des folgenden Listings überfliegen, sollte die prinzipielle Vorgehensweise eigentlich auf Anhieb klar sein.
# Beispieldatei openai/comment-files.py
#!/usr/bin/env python3
import os
from openai import OpenAI
client = OpenAI() # liest die .env-Datei
# sucht rekursiv alle Dateien mit vorgegebenen Kennungen
def find_files_with_extensions(directory, extensions):
matching_files = []
for root, _, files in os.walk(directory):
for file in files:
if any(file.endswith(ext) for ext in extensions):
matching_files.append(os.path.join(root, file))
return matching_files
# liest eine Code-Datei, fügt Kommentare hinzu und speichert sie
def comment(filename):
# Datei lesen, Backup erstellen
with open(filename, "r") as file:
content = file.read()
with open(filename + ".bak", "w") as backup:
backup.write(content)
# Rolle und Prompt zusammensetzen
role = """You are a coding assistant. Your goal ..."""
prompt = f"""Please add comments to the
following code:\n\n{content}"""
try:
completion = client.chat.completions.create(
model = "gpt-4o",
temperature = 0.3,
messages = [ {"role": "system", "content": role},
{"role": "user", "content": prompt} ] )
except Exception as e:
print(" An error occurred:", str(e))
return
# Ergebnis auswerten, veränderte Code-Datei speichern
if completion.choices[0].finish_reason != "stop":
print(" Commenting failed.")
print(" Input Tokens:", completion.usage.prompt_tokens)
print(" Output Tokens:",
completion.usage.completion_tokens)
else:
new_code = completion.choices[0].message.content.strip()
with open(filename, 'w') as file:
file.write(new_code)
print(" Commenting done.")
print(" Input Tokens:", completion.usage.prompt_tokens)
print(" Output Tokens:", completion.usage.completion_tokens)
# Main
dir = "/Users/kofler/my-sample-files"
extensions = [".js", ".php"]
files = find_files_with_extensions(dir, extensions)
for filename in files:
print(filename)
comment(filename)
Für die Funktionsweise dieses Scripts ist die Formulierung der Rolle entscheidend. Dort können Sie angeben, ob das Sprachmodell eher mehr oder wenig kommentieren soll, ob beim Kommentieren eine spezielle Syntax (z. B. JavaDoc) eingehalten werden soll usw.
Tipp
Wenn Sie das Script mit eigenen Dateien ausprobieren möchten, kopieren Sie Ihren Code in ein eigenes Verzeichnis und entfernen dort die schon vorhandenen Kommentare. (Sie gehören ja sicher zu den Entwicklern, die brav kommentieren, oder?)
Ein praktisches Hilfsmittel zum Entfernen von Kommentaren ist cloc. Mit cloc --strip-comments=nc myfile.py erzeugen Sie die neue Datei myfile.py.nc, die frei von Kommentaren ist. Anstelle der Kennung .nc (no comments) können Sie eine beliebige Buchstabenkombination verwenden.
Praktische Erfahrungen
Wir haben das Kommentier-Script nicht nur mit der OpenAI-API ausprobiert, sondern auch mit Ollama und Groq. Entsprechende Scripts sind in den Beispieldateien zum Buch enthalten. Die erzielten Ergebnisse waren durchwachsen:
-
OpenAI mit GPT-4o: Bei kleinen Code-Dateien bis ca. 10 kByte hat die automatische Kommentierung recht gut funktioniert. Natürlich kann auch ein Sprachmodell nicht hellsehen, aber in vielen Fällen waren die Kommentare inhaltlich passend und zumindest als erste Hilfe zum Verständnis des Codes geeignet.
Über den Nutzen mancher Kommentare kann man natürlich streiten. Wenn eine selbst definierte JS-Funktion updateTopicsWithChapters heißt und das Sprachmodell in der Zeile darüber den Kommentar Function to update topics with chapters hinzufügt, ist nicht viel gewonnen. Aber es gibt auch positivere Beispiele. Bei unseren PHP-Testdateien hat die KI bei jeder Methode eine Beschreibung aller Parameter, des Rückgabewerts und eventueller Exceptions hinzugefügt.
Als Hauptproblem bei der OpenAI-API hat sich deren Begrenzung auf 4096-Output-Token herausgestellt. Wir haben nach verschiedenen Wegen gesucht, dieses Limit zu umgehen (siehe die Überschrift »Ärger mit großen Code-Dateien« zwei Seiten weiter), sind aber gescheitert.
-
OpenAI mit GPT-4o-Mini: Bei unseren Tests war gpt-4o-mini überraschenderweise kaum schneller als gpt-4o. Die Qualität der Kommentare war vergleichbar. Erfreulicherweise kommt gpt-4o-mini mit viel größeren Dateien zurecht, weil die maximale Anzahl der Output-Token viel höher ist. Gerade bei langen Dateien war gpt-4o-mini allerdings sehr sparsam bei der Kommentierung und beschränkte sich gerade einmal auf einzeilige Beschreibungen der Funktionen.
-
Ollama: Auf dem Testrechner (MacBook mit M3-Pro-CPU) des Autors dieses Kapitels kann Ollama nur relativ kleine LLMs mit bis zu 10 Milliarden Parameter in einer vernünftigen Geschwindigkeit ausführen – also z. B. llama3:8b. Wir haben auch mit gemma2, codellama und codestral experimentiert.
Die getesteten LLMs waren, vor allem bei größeren Dateien, mit der Aufgabenstellung überfordert. Immer wieder passierte es, dass die Antwort nicht den mit Kommentaren versehenen Code enthielt, sondern eine relativ kurze textuelle Beschreibung der Code-Datei. Das ist eine Themenverfehlung.
-
Groq: Die Kommentierung kleinerer Code-Dateien mit Sprachmodellen, die auf den Servern von Groq gehostet sind, funktionierte ausgezeichnet. Das ist insofern verblüffend, als dort zum Teil dieselben Sprachmodelle wie bei Ollama zum Einsatz kamen, wenn auch mit größeren Kontextfenstern und mit mehr Parametern (z. B. llama3-70b-8192 oder mixtral-8x7b-32768).
Bei großen Code-Dateien versagte auch Groq. In diesem Fall lag es am Rate-Limit des Developer-Zugangs. Die Kommentierung von nur einer großen Code-Datei überschreitet bereits das Limit für Tokens/Minute und wird daher abgelehnt.
Wir hätten unsere Tests gerne ohne Rate-Limit wiederholt, wären auch bereit gewesen, für einen entsprechenden Test-Account zu zahlen, aber unsere diesbezüglichen Anfragen wurden erst beantwortet, als der Text für dieses Buch schon abgeschlossen war. Groq scheint sich eher an große Enterprise-Kunden und weniger an kleine Entwickler zu richten.
Finetuning
Um bessere Ergebnisse zu erzielen, können Sie ein Set von Beispieldateien vorbereiten, einmal unkommentiert, einmal mit Kommentaren, die Sie selbst als optimal empfinden. Aus diesen Beispieldateien bilden Sie Prompt-Response-Paare, die Sie in das messages-Array einbetten. Damit geben Sie dem Sprachmodell einige Muster vor, an denen es sich orientieren kann (auch was die gewünschte Formatierung der Antwort betrifft).
Wirklich überzeugende Ergebnisse hat dieser Ansatz bei unseren Tests allerdings nicht geliefert. Wir hatten vielmehr den Eindruck, dass die zusätzlichen Beispiele im Kontextfenster das Sprachmodell eher verwirrten, vor allem wenn die Beispiele inhaltlich nichts mit den später zu verarbeiteten Code-Dateien zu tun hatten bzw. gar in einer anderen Programmiersprache vorlagen. Zudem erhöht dieser Ansatz die Kosten, weil die Beispiele bei jedem API-Aufruf mitgesendet werden müssen und so die Anzahl der Input-Token stark erhöhen.
Die gleiche Idee können Sie zielbringender verfolgen, wenn Sie Ihre Beispielsammlung zum Finetuning des Sprachmodells verwenden. Hintergründe und Tipps zur Vorgehensweise für das Finetuning von OpenAI-Sprachmodellen bzw. für lokale Sprachmodelle (Ollama) finden Sie auf den folgenden Seiten:
https://openai.com/index/gpt-4o-fine-tuning
https://github.com/ollama/ollama/issues/2488
https://huggingface.co/docs/transformers/training
Code aus der Antwort extrahieren
Einzig GPT-4o und GPT-4o-mini lieferten konsistent wie von uns gewünscht den Code ohne Markdown-Formatierung und ohne einleitenden Text (beispielsweise »Here is the code with added comments«) oder nachgestellte Erklärungen. Bei allen anderen von uns getesteten Sprachmodellen kam es immer wieder vor, dass die Antwort auf unterschiedliche Art und Weise ausgeschmückt wurde. Wir haben versucht, den Code mit der Python-Funktion extract_code aus der Antwort zu extrahieren. Bei unseren Tests hat das zwar zuverlässig funktioniert, aber es ist natürlich kein gutes Zeichen, wenn die »Intelligenz« des Sprachmodells nicht ausreicht, um die Aufgabenstellung zu verstehen.
# in der Beispieldatei groq/comment-files.py
def extract_code(md):
md = md.strip()
lines = md.split("\n")
first, last = 0, len(lines)
for i in range(len(lines)):
if lines[i].startswith("```"):
first = i + 1
break
if first == 0:
return md
else:
print(" Markdown found")
for i in range(first + 1, len(lines)):
if lines[i].startswith("```"):
last = i
break
return "\n".join(lines[first:last])
Echte Fehler
Bei unseren Tests sind wir nur ein einziges Mal auf einen gravierenden Fehler gestoßen: GPT-4o versuchte, den in eine PHP-Datei eingebetteten SQL-Code zu kommentieren. Das wäre an sich in Ordnung gewesen, wenn es die Kommentare dem SQL-Standard entsprechend mit -- eingeleitet hätte. Stattdessen hat GPT-4o // verwendet. Das SQL-Kommando löst damit einen Fehler aus.
Wir haben das folgende Listing stark vereinfacht, um das Problem zu illustrieren. Tatsächlich handelte es sich um ein ziemlich komplexes, mehr als 20 Zeilen langes SELECT-Kommando.
// vorher
$sql = sprintf("
SELECT n.*, CONCAT(p.firstname, ' ', p.lastname) AS fullname
FROM notes n
JOIN person p ON p.id = n.author
WHERE n.topic = %u
ORDER BY n.ts", topic_id);
// nachher
$sql = sprintf("
SELECT n.*, CONCAT(p.firstname, ' ', p.lastname) AS fullname
FROM notes n
JOIN person p ON p.id = n.author
WHERE n.topic = %u
// sort by timestamp <-- fehlerhafter Kommentar!
ORDER BY n.ts", topic_id);
Auch wenn dieser Fehler ein Einzelfall war (normalerweise wird SQL-Code korrekt behandelt, auch wenn er in Code-Dateien mit anderen Programmiersprachen eingebettet ist), beweist das Beispiel, dass Sie die automatisiert eingefügten Kommentare immer überprüfen müssen. Am besten gelingt das, wenn Sie einen Editor wie VS Code verwenden und die kommentierte Datei mit ihrem Zustand beim letzten Commit vergleichen.
Ärger mit großen Code-Dateien
Das Hauptproblem an diesem Beispiel besteht darin, dass sämtliche APIs aus unterschiedlichen Gründen mit großen Code-Dateien überfordert sind. Wir haben diverse Varianten des vorhin abgedruckten Scripts erstellt, in denen wir diese Einschränkung zu umgehen versuchten. (Diese Experimente haben wir nur mit der OpenAI-API durchgeführt, nicht mit den anderen APIs.)
Leider sind alle Versuche gescheitert:
-
Naheliegend ist es, die Code-Datei in Stücke zu zerlegen, also Zeile 1 bis 100, dann Zeile 101 bis 200 usw. Mit jedem Aufruf bekommt das Sprachmodell 100 Zeilen und soll diesen Kommentare hinzufügen.
Das ist aber keine gute Idee. Zum einen geht der inhaltliche Zusammenhang verloren. Das Sprachmodell kann den Code nicht mehr in seiner Gesamtheit beurteilen, kann also z. B. auf Basis der ersten 100 Zeilen nicht abschätzen, was der Gesamtzweck des Codes der ganzen Datei ist. Zum anderen wird der Code oft an ungünstigen Stellen zerrissen. Nicht zusammenpassende Klammerebenen, nicht geschlossene Zeichenketten usw. machen die Code-Stücke syntaktisch fehlerhaft. Wenn man dem Sprachmodell menschliche Züge andichten wollte, könnte man sagen, diese Fehler bringen das Modell aus dem Gleichgewicht. Entgegen allen Anweisungen versucht es, den Code richtigzustellen.
-
Etwas schlauer ist es, dem Sprachmodell jedes Mal die gesamte Code-Datei zu übergeben. Das erfordert ein Sprachmodell mit einem ausreichend großen Kontextfenster. Der Wunsch nach viel Kontext ist weit verbreitet, insofern preisen immer mehr neue Modelle ihre Fähigkeit an, effizient mit einem großen Kontext umzugehen. Beispielsweise erlaubt GPT-4o eine Kontextgröße von 128k Token. Das reicht selbst für riesige Dateien.
Im Prompt bitten Sie das Modell nun, beim ersten Aufruf nur Zeile 1 bis 100 zu kommentieren und zurückzugeben, beim nächsten Mal 101 bis 200 usw. Der wesentliche Unterschied zur vorigen Strategie besteht darin, dass das Sprachmodell immer Zugriff auf die ganze Code-Datei hat, auch wenn es jedes Mal nur einen Zeilenbereich davon bearbeitet und zurückgibt.
Unsere Versuche sind vor allem deswegen gescheitert, weil sich GPT-4o nicht exakt an die Zeilennummern gehalten hat. Beispielsweise sollte das Modell die Zeilen 1 bis 100 bearbeiten. Bei Zeile 90 begann eine Methode, deren Code bis Zeile 110 reichte. Das Modell hat die ganze Methode berücksichtigt und zurückgegeben. Und damit der Code syntaktisch korrekt ist, hat es nach der Methode noch eine Klammer zum Schließen der Klasse hinzugefügt. Diese Klammer, die an dieser Stelle in der Ursprungsdatei nicht vorkommt, ist aber zu viel, wenn später einzelne Code-Stücke wieder zusammengesetzt werden sollen.
-
Unsere nächste Idee bestand darin, das Sprachmodell aufzufordern, statt des neuen, kompletten Codes nur DIFF-Patches zurückzugeben. Das hat zwei Vorteile: Erstens enthalten die Patches nur die Änderungen, sind daher kleiner und beanspruchen weniger Token. Ohne das Output-Token-Limit zu überschreiten, kann mehr Code auf einmal verarbeitet werden.
Zweitens dachten wir, die Anwendung der Patches wäre robuster als das Zusammenfügen der Code-Stücke aus Variante 2. Falsch gedacht: Die Qualität der Patches war mangelhaft, viele Änderungen ließen sich nicht korrekt auf die ursprüngliche Code-Datei anwenden. Die Fehler waren teilweise trivial; oft war einfach die Zeilennummer falsch. Wir haben es dann mit Context-Patches bzw. Unified-Patches versucht (diff-Optionen -c bzw. -u). Das hat die Sache nicht besser gemacht. Sprachmodelle denken offenbar ebenso wenig in DIFF-Patches wie Menschen.
An dieser Stelle haben wir unsere Experimente aufgegeben. Letzten Endes geht offenbar kein Weg daran vorbei, jede Code-Datei in ihrer Gesamtheit zu bearbeiten. Da hilft es nur, auf Sprachmodelle oder APIs zu warten, die ein großes Kontextfenster haben und eine hohe Anzahl von Output-Token erlauben. Gut möglich, dass derartige Angebote weit verbreitet sind, bis Sie dieses Buch lesen.
Vorhandene Kommentare übersetzen
Eine Variante zur ursprünglichen Aufgabenstellung ist die Übersetzung von Kommentaren. Viele Projekte beginnen klein. Kommentare werden (wenn überhaupt) in der jeweiligen Landessprache verfasst. Wenn das Projekt dann nach zehn Jahren überraschend Erfolg hat und vor dem Verkauf steht, machen die deutschen (oder französischen, italienischen) Kommentare im Code plötzlich keinen guten Eindruck mehr. Sie stehen einer Weiterentwicklung des Codes durch ein internationales Team im Weg und mindern den Wert der Software.
Im Prinzip ändert sich am Aufbau des vorhin präsentierten Scripts nichts. Lediglich die Rollenbeschreibung und Prompt-Formulierung muss adaptiert werden:
# Beispieldatei openai/translate-comments.py
...
role = """You are a coding assistant.
Your goal is to translate non-English comments into English.
Apart from comments, leave the code as it is.
The functionality of the code MUST NOT be changed.
DO NOT ADD EXPLANATIONS in your response.
DO NOT USE MARKDOWN to format your response."""
prompt = f"""In the following code, please replace the German
comments with English comments:\n\n{content}"""
...
Unseren Tests mit der OpenAI-API (GPT-4o) waren absolut zufriedenstellend – wenn auch im Rahmen der bereits beschriebenen Einschränkungen. Das Script scheitert also an Code-Dateien, deren Größe das Kontextfenster oder die Anzahl der Output-Token überschreitet.
In der Realität sind oft nicht nur die Kommentare nicht englisch, sondern auch Variablen- und Funktionsnamen, Tabellen- und Spaltennamen usw. Angesichts der bisherigen Erfahrungen brauchen wir hier hoffentlich nicht auszuführen, dass eine automatisierte Anglisierung der Code-Basis Ihres Projekts meilenweit von den Möglichkeiten aktueller KI-Tools entfernt ist.
Das Hauptproblem besteht darin, dass die Änderung eines Methodennamens in der Datei einer Klasse auch in allen anderen Dateien nachvollzogen werden muss, die diese Klasse verwenden. Daher ist es unmöglich, jede Datei für sich zu behandeln. Vielmehr muss die Code-Basis in ihrer Gesamtheit berücksichtigt werden. Gute Editoren und IDEs sind dazu in der Lage. Großartig wäre eine Schnittstelle zwischen Editor und KI-API, um die Fähigkeiten beider Tools zu kombinieren. Das ist vorerst Zukunftsmusik.
Fazit
Wir haben die Arbeiten an diesem Abschnitt mit der Erwartung begonnen, hier ein einfaches und dennoch nützliches Beispiel präsentieren zu können. Damit haben wir uns gründlich getäuscht.
-
Viele LLMs sind nicht einmal konsistent in der Lage, die in der Systembeschreibung (Rolle) und im Prompt genau definierte Aufgabe zu erfüllen.
-
Die Kommentierung kleinerer Dateien funktioniert einigermaßen gut. Bei großen Dateien gelingt die Kommentierung nur, wenn das Kontextfenster groß genug ist, um die gesamte Datei zu verarbeiten, und wenn die Antwortgröße nicht limitiert ist.
-
In zum Glück seltenen Fällen haben die Sprachmodelle den Code so verändert, dass er danach nicht mehr richtig funktioniert hat. Eine gründliche Kontrolle der Änderungen ist absolut unerlässlich.
-
Von ChatGPT & Co. sind wir gewöhnt, dass Sprachmodelle nahezu verzögerungsfrei funktionieren. Von dieser Vorstellung müssen Sie sich verabschieden, sobald Sie die hier präsentierten Scripts auf zehn, hundert oder womöglich noch mehr Dateien anwenden. Die Ausführung dauert je nach API bzw. je nach eigener Hardware minutenlang, im schlimmsten Fall stundenlang. Das macht den Test- und Optimierungsprozess mühsam.