Am eigenen Schopf

Neulich wollte ich einen längeren Text mit ChatGPT diskutieren. Allerdings gibt es in dessen Version 4.0 derzeit eine Prompt-Begrenzung auf 2048 Zeichen. Naja, kein Problem, dachte ich, da soll mir doch ChatGPT ein Programm dafür schreiben, und sich selbst am eigenen Schopf aus dem Gefängnis seiner Begrenzung befreien. Allerdings sollte das nicht ein simpler Split sein, sondern mit Überschriften für die einzelnen Teile und der Möglichkeit, sowohl einzelne Dateien zu erzeugen, als auch eine Gesamtdatei, die alle Teile, getrennt durch Überschriften, enthält. Außerdem sollte das Programm nach Möglichkeit nicht innerhalb einer Zeile umbrechen.

(Bestehende Tools legen einzelne Dateien ab, aber das ist ziemlich schwierig zu handhaben, wenn man die einzelnen Stücke in den Prompt einfügen will. Für so etwas ist eine Gesamtdatei mit Separatoren leichter zu bearbeiten.)

Jedenfalls, das erwies sich zunächst als gar nicht so einfach, ChatGPT servierte mir erstmal zwei Entwürfe, die beide mit oom-kills endeten (mein Rechner ging komplett in die Knie dabei, und ich habe mich schon gefragt, ob ich etwa gerade gehackt werde 😀). Dann kam aber doch eine funktionierende Version dabei heraus, die, wie ich meine, einen zwar ungewöhnlichen, aber dennoch sehr interessanten Ansatz verfolgt, um die Anforderungen zu erfüllen. Ich hatte dabei sozusagen die Rolle des Projektleiters, diktierte meine Anforderungen an die Software, aber ich war auch Tester und Mentor, d.h. ich habe auf Fehler und wünschenswerte Features hingewiesen. Nun, das dauerte zwar ein wenig, aber dann war die erste Version fertig, schneller, als ich das selbst hinbekommen hätte, und außerdem weitaus effizienter als mit Stack Overflow. Diese erste Version hatte dann nur noch einen Fehler, wenn eine Zeile länger war als die Begrenzung, erzeugte das Skript einen Teil, der die Begrenzung überschritt.

Also gab ich ChatGPT den Auftrag, eine Lösung dafür zu finden, und erklärte ihm, dass er Zeilen, die länger als die Begrenzung sind, eben doch umbrechen soll, mit einem (optischen) Hinweis auf die Fortsetzung im nächsten Teil, und dass zusätzlich nicht mitten in einem Wort getrennt werden soll. Interessanterweise landete ChatGPT dabei dann völlig im Wald und erwies sich als hoffnungslos überfordert, eine Lösung für dieses Problemchen zu programmieren. Am Ende war sogar der Code völlig zerstört (nichts funktionierte mehr), und ChatGPT gab irgendwann tatsächlich auf (es erzeugte keinen neuen Code mehr).

Nun ja. Das kleine Python-Tool finden Sie untenstehend. Vielleicht können Sie es einmal brauchen, es eignet sich übrigens auch für Twitter u.dgl. (Für Twitter etc. müsste man aber mit –max-chars eine andere Begrenzung setzen, der voreingestellte Default ist für ChatGPT-Prompts).

Den Teil, den ChatGPT nicht geschafft hat, habe ich dann eben selbst hinzugefügt (er ist mit blauer Schrift gekennzeichnet). Ich fand das insgesamt eine recht spannende Erfahrung. Eine Fragestellung, die ich durchaus als nicht so trivial empfand, wurde mit einigen Anläufen und Hilfestellung (z.B. ist das RegEx-Muster zur Erkennung der Teiler von mir – das muss unbedingt eindeutig sein, damit es nicht versehentlich falsch aufteilt, und das Muster, das ChatGPT zuerst vorschlug, hatte diese Gefahr) recht ordentlich gelöst. Aber bei etwas, das auf mich nicht allzu kompliziert gewirkt hat (und es dann auch nicht war), verrannte sich ChatGPT völlig. Auch dass ChatGPT den ursprünglich funktionierenden Entwurf bei diesem Fehlerbehebungsversuch völlig zerstörte, zeigt m.Mng., dass ChatGPT zwar eine große Hilfe beim Programmieren sein kann, aber ohne die Zusammenarbeit und die Hilfe eines menschlichen Entwicklers ist es, zumindest bisher, aussichtslos. Das wird dauern, bis ChatGPT Programmierer ersetzen kann – falls das jemals überhaupt eintreten wird. Die sich selbst weiter entwickelnde KI ist noch in weiter Ferne, würde ich meinen.

import argparse, os, re

def split_text(input_filename, output_filename, max_chars, delimiter, single_output, replace_input):
    with open(input_filename, 'r') as file:
        text = file.read()

    paragraphs = text.split(delimiter)
    result = []
    part_count = 1
    result.append(f"Teil {part_count:02}\n{'='*20}\n")
    char_count = len("Teil 01\n" + "=" * 20 + "\n")
    header_length = len(f"Teil {part_count:02}\n" + "=" * 20 + "\n")

    for paragraph in paragraphs:
        if char_count + len(paragraph) + len(delimiter) > max_chars:
            part_count += 1
            result.append(f"Teil {part_count:02}\n{'='*20}\n")
            char_count = header_length
            
        while len(paragraph) + header_length > max_chars:
            slice_pos = max_chars-header_length
            while slice_pos >= 0:
                if paragraph[slice_pos] == " ":
                    break
                slice_pos -= 1
            if slice_pos == 0:
                slice_pos = max_chars-header_length
            else:
                slice_pos += 1
                pglist = list(paragraph)
                pglist[slice_pos-1] = '>'
                paragraph = ''.join(pglist)
            result.append(paragraph[:slice_pos])
            result.append(delimiter)
            paragraph=paragraph[slice_pos:]
            part_count += 1
            result.append(f"Teil {part_count:02}\n{'='*20}\n")
            char_count = header_length

        result.append(paragraph)
        result.append(delimiter)
        char_count += len(paragraph) + len(delimiter)

    if single_output:
        if replace_input:
            os.remove(input_filename)
        with open(output_filename, 'w') as file:
            file.write("".join(result))
    else:
        base, ext = os.path.splitext(output_filename)
        for i, part in enumerate(re.split("Teil\s\d+(?=\n)\n====", "".join(result))[1:]):
            with open(f"{base}-{i+1:02}{ext}", 'w') as file:
                file.write(f"Teil {i+1:02}\n===={part}")

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="Split text files into smaller parts.")
    parser.add_argument('input', help="Input filename")
    parser.add_argument('-o', '--output', help="Output filename", default=None)
    parser.add_argument('-m', '--max-chars', type=int, help="Maximum number of characters per part", default=2048)
    parser.add_argument('-d', '--delimiter', help="Delimiter between paragraphs", default="\n\n")
    parser.add_argument('-s', '--single-output', action='store_true', help="Store the result in a single output file")
    parser.add_argument('-r', '--replace-input', action='store_true', help="Replace the input file with the result")

    args = parser.parse_args()

    if args.output is None and not args.replace_input:
        parser.error("Output filename must be provided if not using --replace-input")

    output_filename = args.output if args.output is not None else args.input
    
    split_text(args.input, output_filename, args.max_chars, args.delimiter, args.single_output, args.replace_input)

Sie können das Script entweder aus obiger Box herauskopieren, oder eine Archivdatei herunterladen. (OpenAI erhebt keinerlei Anspruch auf Copyright etc. und stimmt deshalb der Veröffentlichung zu).

Noch zwei Hinweise: Sollte bei Ihnen eine Teildatei auf Platte größer werden als die Begrenzung, liegt das vermutlich an Umlauten, bzw. daran, dass UTF8 für gewisse Zeichen auf Platte zwei Bytes erzeugt. Der tatsächliche Text in einer Teildatei wird aber (incl. Überschrift und Trenner) niemals größer als die Begrenzung. Außerdem müsste man eigentlich noch den Fall abgefangen, dass max_chars kleiner angegeben wäre als die Länge der Separatoren, aber dazu war ich zu faul, wer würde schon so kleine Teile brauchen?

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert