Der Allesschreiber

OpenAIs GPT-4 als Ghostwriter für Vollautomatische Artikelgenerierung

Bild von OpenAIs Sora generiert. Prompt: "Draw an image of a robot working at a lab bench, surrounded by books and papers. The robot is holding a tablet or laptop computer. In the background, there is a whiteboard with scientific equations and diagrams. Shiny futuristic world; bright futuristic office; lens flare; Window in Background"

Artikelgenerierung mit GPT-4

Die "Dead Internet Theory" behauptet, dass das Internet heute weitgehend leer und künstlich sei. Die meisten Interaktionen, insbesondere in sozialen Netzwerken, finden zwischen Bots statt: generierte Inhalte, generierte Klicks, generierte Likes. Warum? Um Geld zu verdienen mit Werbung, Affiliate-Links und dem Verkauf von Daten.

AI will not replace you, but the person using AI will.

In diesem Artikel geht es um die vollautomatische Generierung von Webseiten inklusive Bildern mittels künstlicher Intelligenz. Das Ghostwriter-Skript ist ein Python-Skript, welches die OpenAI Programmierschnittstelle verwendet, um Inhalte zu generieren. Für die Erstellung einer Webseite werden drei Dinge benötigt. Ein Inhaltsverzeichnis, der Text und die Bilder.

Aktuelle KI Modelle sind nicht in der Lage lange Texte in einem Schritt zu generieren. Sie können nur Abschnitte von Texten generieren, die dann zu einem größeren Text zusammengesetzt werden müssen. Für jedes der drei Elemente gibt es daher im Skript eine separate Klasse, welche entsprechende Anfragen an das Sprachmodell sendet. Die Anfragen werden über die Programmierschnittstelle an GPT-4 gesendet und dort bearbeitet.

Der zentrale Bestandteil des Ghostwriter-Skripts ist die query()-Funktion. Sie kapselt den kompletten Ablauf einer Kommunikation mit der OpenAI-API. Dabei ist wichtig zu verstehen, dass GPT-Modelle zustandslos arbeiten. Jede Anfrage muss den gesamten Kontext beinhalten, den das Modell kennen soll. Die OpenAI-API erwartet daher eine Liste von Nachrichten (Role-Message-Paare).

def query(self, prompt, temp=0.3, freq_penalty=0.3, pres_penalty=0.2, max_tokens=4000, model=None) -> str:
	if self.role is None or self.role == "":
		raise ValueError("Role is not set. Please provide a valid role.")

	# Send the prompt to OpenAI's chat completion endpoint
	response = self.client.chat.completions.create(
		model=model or self.__model,
		messages=[
			{"role": "system", "content": self.role},
			{"role": "user", "content": prompt}
		],
		temperature=temp,
		max_tokens=max_tokens,
		top_p=1.0,
		frequency_penalty=freq_penalty,
		presence_penalty=pres_penalty
	)

	result : str | None = response.choices[0].message.content
	if result is None:
		raise ValueError("No response from OpenAI API.")

	return result.strip()

Parameter

  • model: Die aktuell besten Modelle sind gpt-4 oder gpt-4-turbo.
  • system-role: Definiert das Verhalten und die "Persönlichkeit" der KI. Beispiel: "You are a scientific article generator."
  • prompt: Die Nutzereingabe bzw. eigentliche Aufgabe, z. B. ein zu schreibender Absatz oder eine Strukturvorgabe.
  • temperature: Zufallsfaktor. 0 = deterministisch, 1 = maximal kreativ. 0.3 liefert konsistente Ergebnisse.
  • frequency_penalty: Bestraft Wiederholungen einzelner Tokens. Reduziert Floskeln und Redundanzen.
  • presence_penalty: Bestraft Wiederverwendung bereits erwähnter Tokens. Führt zu größerer Themenvielfalt.
  • max_tokens: Obergrenze für die Antwortlänge. Beachten: Eingabe + Ausgabe zusammen dürfen das Limit nicht überschreiten.

Vollautomatisches Erzeugen einer Webseite

Die Generierung des eigentlichen Artikels erfolgt auf Basis der zuvor erstellten XML-Datei mit dem Inhaltsverzeichnis. Der Ablauf ist vollständig automatisiert: Für jede Überschrift im Inhaltsverzeichnis wird ein passender Text von GPT-4 erzeugt und direkt in HTML eingebettet. Die Struktur des Artikels ergibt sich dabei aus der XML-Datei.

Inhaltsverzeichnis

Der Erste Prompt erzeugt ein Inhaltsverzeichnis für den Artikel im XML-Format. Zu diesem Zweck wird das exakte Format des Inhaltsverzeichnisses in Form eines XSD-Schemas vorgegeben:

Create a well-structured XML table of contents for an article titled "{topic}".
- Include a maximum of 3 to {nchapter} main chapters.
- Each chapter must have 2 to 4 sub-subsection. Make sure to not always use the same number of subsections.
- Use the provided XSD schema exactly.
- Each "Caption" must consist of two or more words.
- You are not allowed to add any non xml text either before or after the XML as this will break the toolchain.

<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="outline">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="topic" type="xs:string" />
        <xs:element maxOccurs="unbounded" name="subtopic">
          <xs:complexType>
            <xs:sequence>
              <xs:element maxOccurs="unbounded" name="subsubtopic">
                <xs:complexType>
                  <xs:attribute name="Caption" type="xs:string" use="required" />
                </xs:complexType>
              </xs:element>
            </xs:sequence>
            <xs:attribute name="Caption" type="xs:string" use="required" />
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

Die Rückgabe enthält eine XML-Datei mit dem maschinenlesbaren Inhaltsverzeichnis für den Artikel. Dieses wird im nächsten Schritt für die Generierung der einzelnen Kapitel verwendet werden.

<?xml version="1.0" encoding="utf-8"?>
<outline>
  <topic>The History of Space Exploration</topic>
  <subtopic Caption="Early Rocket Science">
    <subsubtopic Caption="Origins of Rocketry"/>
    <subsubtopic Caption="World War II Contributions"/>
    <subsubtopic Caption="Post-War Developments"/>
  </subtopic>
  <subtopic Caption="The Space Race">
    <subsubtopic Caption="Sputnik Launch"/>
    <subsubtopic Caption="Moon Landing"/>
    <subsubtopic Caption="Soviet vs. USA Milestones"/>
  </subtopic>
  <subtopic Caption="Modern Space Missions">
    <subsubtopic Caption="International Space Station"/>
    <subsubtopic Caption="Mars Rovers and Research"/>
  </subtopic>
  <subtopic Caption="Commercial Spaceflight">
    <subsubtopic Caption="Rise of Private Companies"/>
    <subsubtopic Caption="Tourism in Space"/>
  </subtopic>
</outline>

Textgenerierung

Die zentrale Funktion zur Texterzeugung ist __write_introduction(). Sie erstellt Prompts auf Basis von Kapitel- und Abschnittsüberschriften und übergibt diese an die query()-Funktion:

def __write_introduction(self, topic, chapter, subsection, nwords=100):
    if chapter is None:
        prompt = f'{topic}; Introductory text with at least {nwords} words; [Format:HTML;No heading;use <p> tags]'
    elif subsection is None:
        prompt = f'Topic: {chapter} / {topic}; Introductory; {nwords} Words; [Format:HTML;No heading;use <p> tags]'
    else:
        prompt = f'[Caption: {topic}][Topic: "{chapter}:{subsection}"][Format:HTML;No heading;use <p> tags][Write: min. {nwords} words]'

    return self.query(prompt, temp=0.3, freq_penalty=0.5, pres_penalty=0.5, max_tokens=4000)

Die Prompts sind möglichst kurz gehalten. Mit diesen erzeugt das Sprachmodell direkt HTML-Inhalt für Kapitel und Unterkapitel. Die Erzeugung geschieht in der Funktion __create_article_from_toc().

def __remove_first_paragraph(self, html):
    paragraphs = html.split('</p>')
    fixed_html = '</p>'.join(paragraphs[1:]).strip()
    return html if not fixed_html else fixed_html


def __create_article_from_toc(self, toc_tree):
    if toc_tree is None:
        raise ValueError("Table of contents is None. Cannot create article.")
    
    root = toc_tree.getroot()
    if root is None:
        raise ValueError("Root element is None. Cannot create article.")
    
    article_code = ""
    for element in root:
        if element.tag == "topic":
            topic = element.text
            article_code += f'\r\n<section><h1>{topic}</h1>'
            article_code += f'<span class="image right">\r\n  <img src="{topic}.jpg"></img>\r\n</span>'
            intro = self.__write_introduction(topic, None, None, 300)
            article_code += intro + '\r\n</section>'

        elif element.tag == "subtopic":
            chapter = element.attrib['Caption']
            article_code += f'\r\n<section><h2>{chapter}</h2>'
            chapter_text = self.__write_introduction(topic, chapter, None, 100)
            article_code += self.__remove_first_paragraph(chapter_text)

            for subelement in element:
                subsection = subelement.attrib['Caption']
                article_code += f'\r\n<h3>{subsection}</h3>'
                article_code += f'<span class="image right">\r\n  <img src="{topic}-{subsection}.jpg"></img>\r\n</span>'
                subsection_text = self.__write_introduction(topic, chapter, subsection, 600)
                article_code += self.__remove_first_paragraph(subsection_text)

            article_code += '\r\n</section>'

    return article_code

Die generierten Absätze werden als HTML-Abschnitte zusammengefügt. Jede Ebene der XML-Struktur (Topic → Subtopic → Subsubtopic) entspricht einem <section>-Block mit <h1>, <h2> bzw. <h3>. Da GPT-4 dazu neigt, im ersten Absatz eines Abschnitts den übergeordneten Kontext erneut zu erklären, entfernt das Skript diesen mit __remove_first_paragraph(). Das verbessert die stilistische Kohärenz des Artikels deutlich.

Bildgenerierung

Zu den Abschnitten werden ebenfalls automatisch passende Bilder erzeugt. Dazu verwendet das Skript das Modell dall-e-3 von OpenAI. Die Generierung erfolgt in zwei Schritten: Zunächst wird ein beschreibender Prompt für das Bild erstellt, anschließend wird dieser Prompt zur Bildgenerierung an die API übergeben. Die Funktion create_image() ruft zunächst GPT-4 auf, um eine passende visuelle Beschreibung zu erzeugen. Der Prompt wird dabei automatisch aus Thema und Kapiteltitel generiert:

prompt = (
    f'Write a concise, visual description for a photorealistic illustration for an article section titled: "{topic}"'
    f'{f" - {chapter}" if chapter else ""}. '
    'Keep it under 600 characters. Describe a simple specific scene with lighting, composition, and mood. '
    'Do not include HTML or quotes.'
)

image_query_prompt = self.query(prompt).strip()
Beispielbild auf Basis des links angegebenen Promptes.

Die resultierende Beschreibung sieht beispielsweise so aus:

Create a photorealistic image of a Cambrian seabed filled with diverse, colorful marine life, under clear blue water with soft sunlight filtering from above.

Dieser von GPT-4 erzeugte Prompt wird anschließend zur Bildgenerierung an das Modell dall-e-3 gesendet. Die API-Antwort enthält eine URL zu dem erzeugten Bild. Dieses wird heruntergeladen und im Ausgabeordner gespeichert. Für jeden Unterabschnitt im Inhaltsverzeichnis wird auf diese Weise ein eigenes Bild erzeugt. Die Dateinamen folgen dem Muster {Thema}-{Unterkapitel}.jpg und können später direkt im HTML referenziert werden.

Templating und Ausgabe

Abschließend wird ein HTML-Template geladen und mit den generierten Inhalten ersetzt. Das Template enthält Platzhalter wie {TOPIC}, {CONTENT} und {VERSION}, die durch konkrete Werte ersetzt werden. Der finale Artikel wird dann als index.html oder index.php im Ausgabeverzeichnis gespeichert und ist sofort einsatzbereit.

Beispiel eines KI-generierte Artikels:

Download und Verwendung

Wer das Ghostwriter-Skript verwenden möchte, der benötigt einen OpenAI API-Key. Dieser muss in der Umgebungsvariable "OPENAI_API_KEY" hinterlegt werden. Die Verwendung von GPT-3 ist kostenpflichtig. Aktuell bietet OpenAI bei Registrierung ein einmaliges Guthaben von 18 Euro an, das man aufbrauchen kann. Die Kosten für die Erstellung eines Artikels mit Bildern liegen im Centbereich.

Download Ghostwriter-Skript via GitHub

Kommandozeilenparameter

Für die Ausführung benötigt man Python 3. Es kann mit folgender Kommandozeile gestartet werden:

python ./ghostwriter.py -t "The Rise of AI generated Content" -tmpl ./template.html -o ai_content
Parameter Description
-t Das Thema der Webseite. Dieser Text sollte in Anführungsstrichen stehen.
-tmpl TEMPLATE Eine Textdatei (beispielsweise HTML oder PHP), welche den Rahmen für die Webseite bereit stellt. Die Datei muss die Platzhalter {TOPIC}, {CONTENT} und optional {VERSION} enthalten. Diese werden vom Skript automatisch mit der Überschrift, dem HTML formatierten Artikel bzw. der Skriptversionsnummer ersetzt.
-o OUTPUT Ausgabeverzeichnis für den Artikel. Wenn dieses Verzeichnis nicht existiert, wird ein neues angelegt.
-s WRITING-STYLE Optional: Ein Text, der den Schreibstil angibt. (z.B. "Carl Sagan", "National Geographic", "PBS" oder "Drunken Pirate")
-v Optional: Wenn dieses Flag gesetzt ist, gibt das Skript die GPT-3 Anfragen in der Konsole aus.