Wenn Sie schon einmal 40 Minuten mit dem Schreiben einer Produktbeschreibung verbracht haben und am Ende nur eine lauwarme Version erhalten haben, werden Sie diesen Workflow lieben: WooCommerce generiert aus dem Titel, den Attributen und einigen geschäftlichen Anmerkungen eine "lange" Beschreibung und einen "kurzen" Snippet – und Sie behalten die Kontrolle über die Validierung.

Der Bedarf / Der Anwendungsfall

In WooCommerce-Katalogen ist die Content-Erstellung oft der Flaschenhals. Ich habe Shops gesehen, in denen 300 Produkte den Status „Ausstehend“ hatten, nur weil die Beschreibungen fehlten, obwohl Fotos und Preise bereits fertig waren. Die Folge: schlechte Suchmaschinenoptimierung, sinkende Konversionsraten und ein Team, das den Launch verzögert.

KI ist hier nützlich, um aus bereits vorhandenen Daten einen kohärenten, strukturierten und gewinnorientierten ersten Entwurf zu erstellen. WordPress : Titel, Kategorien, Attribute, Marke, Abmessungen, Material usw. Ziel ist es nicht, blind zu automatisieren, sondern den Schreibaufwand pro Produkt zu reduzieren und gleichzeitig die redaktionelle Kontrolle zu wahren.

Am Ende werden Sie wissen, wie Sie es umsetzen:

  • Ein „Mit KI generieren“-Button im Produkteditor (WooCommerce-Adminbereich).
  • Ein KI-API-Aufruf über wp_remote_post() (ohne SDK) mit Timeout- und Fehlerbehandlung.
  • Ein Cache pro Transient, um zu vermeiden, dass für dieselbe Generation erneut bezahlt wird.
  • Das sichere Update von lange Beschreibung et Kurzbeschreibung (Auszug) aus dem Produkt.
  • Ein einfaches System zur Begrenzung der Gebühren, um Gebührenspitzen (und unerwartete Rechnungen) zu vermeiden.

Kurze Zusammenfassung

  • Wir fügen eine WooCommerce-Meta-Box mit einem Button hinzu, der eine AJAX-Admin-Anfrage auslöst.
  • Der Server generiert eine Eingabeaufforderung aus den Produktdaten (Attribute, Kategorien usw.).
  • KI-Anruf mit wp_remote_post(), kurzes Timeout, begrenzte Wiederholungsversuche, protokollierte Fehler.
  • Bereinigter Antwort mit wp_kses_post() vor dem Einsetzen in post_content et post_excerpt.
  • Zwischenspeicherung über die Transients API, um eine Neugenerierung zu vermeiden, falls sich nichts geändert hat.
  • API-Schlüssel gespeichert in wp-config.php (niemals fest codiert, niemals auf der JS-Seite).

Wann sollte man KI dafür einsetzen?

Setzen Sie KI ein, wenn Sie über ein großes Datenvolumen, eine zuverlässige Datenstruktur und ein Bedürfnis nach Datenkonsistenz verfügen. Typischerweise:

  • Geschäfte mit mehreren Anbietern wobei die Datensätze in Form von Rohdaten (CSV, ERP) mit wenig Text ankommen.
  • Kataloge mit Variationen (Größe/Farbe) Hier wünschen Sie sich einheitliche Beschreibungen, Unterschiede werden über Attribute verwaltet.
  • SEO-„Baseline“ : Erstellen Sie einen sauberen, gut lesbaren Text mit einer stabilen Struktur (Absätze, Listen) und verfeinern Sie anschließend die strategischen Produkte.
  • Internationalisierung (Erweiterte Variante): Generierung einer EN/DE/ES-Version aus einer FR-Basis mit menschlicher Validierung.

In der Praxis hat es sich bewährt, das Produkt bei seiner Erstellung zu generieren und es anschließend nur dann neu zu generieren, wenn sich bestimmte Felder (Attribute, Kategorie, Marke) ändern. Caching basierend auf dem „Fingerabdruck“ des Produkts ist dabei sehr hilfreich.

Wann man KI NICHT einsetzen sollte

Vermeiden Sie KI, wenn eine traditionelle Lösung robuster oder weniger riskant ist:

  • Streng juristische Beschreibungen (Kosmetik, Nahrungsergänzungsmittel, Medizin): Das Risiko von Halluzinationen ist real. Verwenden Sie vorzugsweise PHP-Templates mit ACF-Feldern/Attributen oder eine rechtlich geprüfte Datenbank.
  • Hochtechnische Produkte Wo selbst kleinste Fehler zu Rücksendungen beim Kundenservice führen (Kompatibilität, Standards). Künstliche Intelligenz kann weiterhin eingesetzt werden, jedoch nur bei einer gesperrten Eingabeaufforderung mit obligatorischer Validierung.
  • Standorte mit sehr strengen Kostenbeschränkungen Wenn Sie 20.000 Produkte haben und diese häufig neu generieren, steigen die API-Kosten schnell an, wenn Sie kein Caching und keine Stapelverarbeitung nutzen.
  • Wenn Ihre Quelldaten schlecht sind (Inkonsistente Titel, leere Attribute). Die KI wird die Lücken automatisch füllen. Beginnen Sie in diesem Fall mit der Bereinigung des Katalogs.

Ein häufiges Anti-Pattern ist das Auslösen einer Generierung bei jedem automatischen Speichern. Das führt zu Dutzenden von API-Aufrufen für ein einziges Produkt, da Gutenberg automatisch speichert, WooCommerce die Meta-Tags aktualisiert und Ihr Hook kaskadenartig ausgelöst wird.

Voraussetzungen

Bis April 2026 sollten Sie mindestens Folgendes anstreben:

  • WordPress 6.9.4 (Ihr Kontext) und PHP 8.1 +.
  • WooCommerce Aktuell (Branch 2026). Der untenstehende Code basiert auf stabilen WordPress-APIs und nicht auf den anfälligen Interna von WooCommerce.
  • Zugriff auf wp-config.php (oder Umgebungsvariablen) zum Speichern des API-Schlüssels.

Speichern Sie den API-Schlüssel (wp-config.php)

Füge dies hinzu zu wp-config.php (idealerweise über die Geheimnisverwaltung Ihres Hosting-Anbieters, aber die Konstante bleibt ein guter Ausgangspunkt).

/**
 * Clé API OpenAI (exemple).
 * Ne la commitez jamais dans Git. Ne la mettez jamais dans un plugin.
 */
define('BPCAB_OPENAI_API_KEY', 'sk-proj-...');

Falls Sie Umgebungsvariablen bevorzugen, können Sie Folgendes tun:

define('BPCAB_OPENAI_API_KEY', getenv('BPCAB_OPENAI_API_KEY') ?: '');

Nützliche offizielle Referenzen

Lösungsarchitektur

Ablauf (textuelles Schema):

Produktverwaltung (Schaltfläche „Generieren“) → AJAX-Verwaltung (Nonce + Funktionen) → Abruf der Produktdaten → Hash-Berechnung → temporärer Cache? → andernfalls wp_remote_post() → KI-API → JSON-Parsing → Bereinigung (wp_kses_post()) → Produktaktualisierung (Inhalt + Auszug) → JSON-Rückgabe an den Administrator

Was geschieht hinter den Kulissen?

  • UI-Administrator Eine Metabox mit einem Button und einem optionalen Notizfeld. Der JavaScript-Code enthält niemals den API-Schlüssel.
  • AJAX-Endpunkt : durch Nonce gesichert + current_user_can('edit_product', $product_id).
  • Prompt : Erstellt aus zuverlässigen Elementen (WooCommerce-Attribute, Kategorien, Tags, Preise, falls gewünscht).
  • Cache : Vorübergehend, basierend auf einem Hash der Daten. Wenn sich nichts geändert hat, wird die bereits generierte Beschreibung zurückgegeben.
  • Desinfektion Die KI kann HTML zurückgeben. Nur das, was WordPress in einem Beitrag zulässt, ist zulässig. wp_kses_post().

Der vollständige Code – Schritt für Schritt

Ich rate Ihnen, sich für ein/e zu entscheiden Mu-Plugin Um zu verhindern, dass ein Child-Theme oder ein Snippet-Plugin während eines Updates beschädigt wird, erstellen Sie Folgendes: wp-content/mu-plugins/bpcab-ai-woo-descriptions.php.

1) Speichern Sie eine Metabox in der Produktverwaltung.

Wir benutzen den Bildschirm productDiese Meta-Box bleibt unabhängig vom verwendeten Frontend-Builder (Divi/Elementor/Avada) kompatibel, da sie auf der WooCommerce-Admin-Seite erstellt wird.

<?php
/**
 * Plugin Name: BPCAB - IA descriptions produits WooCommerce
 * Description: Génère des descriptions produits via IA depuis l'admin WooCommerce (WP 6.9.4+, PHP 8.1+).
 * Version: 1.0.0
 */

if (!defined('ABSPATH')) {
    exit;
}

add_action('add_meta_boxes', function () {
    add_meta_box(
        'bpcab_ai_product_desc',
        'Descriptions IA',
        'bpcab_render_ai_metabox',
        'product',
        'side',
        'high'
    );
});

function bpcab_render_ai_metabox(WP_Post $post): void {
    $product_id = (int) $post->ID;

    if (!current_user_can('edit_product', $product_id)) {
        echo '<p>Vous n’avez pas les droits pour modifier ce produit.</p>';
        return;
    }

    wp_nonce_field('bpcab_ai_generate_desc', 'bpcab_ai_nonce');

    echo '<p>
        <label for="bpcab_ai_notes"><strong>Notes internes (optionnel)</strong></label>
        <textarea id="bpcab_ai_notes" style="width:100%;min-height:70px;" placeholder="Ex: ton premium, mentionner la garantie 2 ans, éviter les superlatifs..."></textarea>
    </p>';

    echo '<p>
        <button type="button" class="button button-primary" id="bpcab-ai-generate" data-product-id="' . esc_attr((string) $product_id) . '">
            Générer avec IA
        </button>
    </p>';

    echo '<div id="bpcab-ai-status" style="margin-top:8px;"></div>';
}

2) Laden Sie ein JS-Skript nur auf dem Produktbildschirm

Ein Fehler, den ich häufig beobachte, ist das Einbinden des Skripts überall im Admin-Panel. Dies verlangsamt den Prozess unnötig und kann zu Konflikten mit anderen Plugins führen.

add_action('admin_enqueue_scripts', function (string $hook_suffix) {
    // Écrans typiques : post.php (édition), post-new.php (création)
    if (!in_array($hook_suffix, ['post.php', 'post-new.php'], true)) {
        return;
    }

    $screen = get_current_screen();
    if (!$screen || $screen->post_type !== 'product') {
        return;
    }

    wp_enqueue_script(
        'bpcab-ai-woo-admin',
        plugins_url('bpcab-ai-woo-admin.js', __FILE__),
        ['jquery'],
        '1.0.0',
        true
    );

    wp_localize_script('bpcab-ai-woo-admin', 'BPCAB_AI', [
        'ajaxUrl' => admin_url('admin-ajax.php'),
        'nonce'   => wp_create_nonce('bpcab_ai_generate_desc'),
    ]);
});

Als Nächstes erstellen Sie die Datei wp-content/mu-plugins/bpcab-ai-woo-admin.jsJa, ein MU-Plugin kann eine separate JS-Datei laden, sofern der Pfad korrekt ist. Falls Ihre Infrastruktur dies verhindert, platzieren Sie die JS-Datei in einem regulären Plugin (das ist oft einfacher).

(function ($) {
  function setStatus(html) {
    $('#bpcab-ai-status').html(html);
  }

  $(document).on('click', '#bpcab-ai-generate', function () {
    var productId = $(this).data('product-id');
    var notes = $('#bpcab_ai_notes').val() || '';

    setStatus('<p>Génération en cours… (ne fermez pas cet onglet)</p>');

    $.ajax({
      url: BPCAB_AI.ajaxUrl,
      method: 'POST',
      dataType: 'json',
      data: {
        action: 'bpcab_ai_generate_product_desc',
        nonce: BPCAB_AI.nonce,
        product_id: productId,
        notes: notes
      }
    })
    .done(function (resp) {
      if (!resp || !resp.success) {
        var msg = (resp && resp.data && resp.data.message) ? resp.data.message : 'Erreur inconnue.';
        setStatus('<p style="color:#b32d2e;"><strong>Échec:</strong> ' + msg + '</p>');
        return;
      }

      setStatus(
        '<p style="color:#1e7e34;"><strong>OK:</strong> Descriptions mises à jour.</p>' +
        '<p>Astuce: cliquez sur “Mettre à jour” pour déclencher les hooks habituels de votre stack (cache, SEO, etc.).</p>'
      );
    })
    .fail(function (xhr) {
      setStatus('<p style="color:#b32d2e;"><strong>Erreur AJAX:</strong> vérifiez la console et vos logs PHP.</p>');
    });
  });
})(jQuery);

3) Erstellen Sie den AJAX-Endpunkt (Sicherheit + Validierung)

Wir prüfen die Nonce, die Berechtigungen und die Produkt-ID. Und wir verhindern Aufrufe, wenn der API-Schlüssel nicht konfiguriert ist.

add_action('wp_ajax_bpcab_ai_generate_product_desc', function () {
    // Vérification nonce
    $nonce = isset($_POST['nonce']) ? sanitize_text_field((string) $_POST['nonce']) : '';
    if (!wp_verify_nonce($nonce, 'bpcab_ai_generate_desc')) {
        wp_send_json_error(['message' => 'Nonce invalide. Rechargez la page produit.'], 403);
    }

    $product_id = isset($_POST['product_id']) ? (int) $_POST['product_id'] : 0;
    if ($product_id <= 0) {
        wp_send_json_error(['message' => 'ID produit invalide.'], 400);
    }

    if (!current_user_can('edit_product', $product_id)) {
        wp_send_json_error(['message' => 'Droits insuffisants.'], 403);
    }

    if (!defined('BPCAB_OPENAI_API_KEY') || !BPCAB_OPENAI_API_KEY) {
        wp_send_json_error(['message' => 'Clé API manquante. Définissez BPCAB_OPENAI_API_KEY dans wp-config.php.'], 500);
    }

    $notes = isset($_POST['notes']) ? wp_kses_post((string) $_POST['notes']) : '';

    $result = bpcab_ai_generate_and_update_product($product_id, $notes);

    if (is_wp_error($result)) {
        wp_send_json_error([
            'message' => $result->get_error_message(),
            'code'    => $result->get_error_code(),
        ], 500);
    }

    wp_send_json_success(['updated' => true]);
});

4) Produktdaten abrufen und die Eingabeaufforderung erstellen

Wir nutzen WooCommerce, sofern verfügbar; andernfalls geben wir eine eindeutige Fehlermeldung zurück. Dies ist kein Hook. save_post Bis der Datenfluss stabilisiert ist: AJAX ist besser vorhersehbar.

function bpcab_ai_generate_and_update_product(int $product_id, string $notes = '') {
    if (!function_exists('wc_get_product')) {
        return new WP_Error('woocommerce_missing', 'WooCommerce ne semble pas actif.');
    }

    $product = wc_get_product($product_id);
    if (!$product) {
        return new WP_Error('product_missing', 'Produit introuvable.');
    }

    // Rate limiting basique (par produit + utilisateur) pour éviter les rafales.
    $user_id = get_current_user_id();
    $rl_key = 'bpcab_ai_rl_' . $user_id . '_' . $product_id;
    if (get_transient($rl_key)) {
        return new WP_Error('rate_limited', 'Vous générez trop vite. Attendez 30 secondes et réessayez.');
    }
    set_transient($rl_key, 1, 30);

    $payload = bpcab_build_product_payload_for_prompt($product);

    // Empreinte: si les données n'ont pas changé, on peut servir depuis cache.
    $fingerprint = hash('sha256', wp_json_encode([
        'payload' => $payload,
        'notes'   => $notes,
        'v'       => '1.0.0', // incrémentez si vous changez la logique de prompt
    ]));

    $cache_key = 'bpcab_ai_desc_' . $product_id . '_' . substr($fingerprint, 0, 12);
    $cached = get_transient($cache_key);
    if (is_array($cached) && isset($cached['long'], $cached['short'])) {
        return bpcab_update_product_descriptions($product_id, $cached['long'], $cached['short']);
    }

    $prompt = bpcab_build_prompt($payload, $notes);

    $ai = bpcab_call_openai_responses_api($prompt);
    if (is_wp_error($ai)) {
        return $ai;
    }

    // Sanitation: on autorise un HTML "post" standard, pas de scripts, pas d'iframes.
    $long = wp_kses_post($ai['long'] ?? '');
    $short = wp_kses_post($ai['short'] ?? '');

    // Fallback si l'IA renvoie vide (ça arrive avec des prompts trop stricts).
    if (mb_strlen(wp_strip_all_tags($long)) < 80) {
        return new WP_Error('ai_empty', 'La réponse IA est trop courte ou vide. Vérifiez vos données produit et le prompt.');
    }
    if (mb_strlen(wp_strip_all_tags($short)) < 30) {
        $short = wp_trim_words(wp_strip_all_tags($long), 35, '…');
    }

    // Cache 30 jours (vous pouvez réduire si votre catalogue change souvent).
    set_transient($cache_key, ['long' => $long, 'short' => $short], 30 * DAY_IN_SECONDS);

    return bpcab_update_product_descriptions($product_id, $long, $short);
}

function bpcab_build_product_payload_for_prompt(WC_Product $product): array {
    $product_id = $product->get_id();

    $cats = wp_get_post_terms($product_id, 'product_cat', ['fields' => 'names']);
    $tags = wp_get_post_terms($product_id, 'product_tag', ['fields' => 'names']);

    // Attributs WooCommerce (pa_*) et attributs custom.
    $attributes_out = [];
    foreach ($product->get_attributes() as $attr) {
        if ($attr->is_taxonomy()) {
            $taxonomy = $attr->get_name();
            $label = wc_attribute_label($taxonomy);
            $terms = wp_get_post_terms($product_id, $taxonomy, ['fields' => 'names']);
            $attributes_out[] = [
                'label' => $label,
                'values' => array_values(array_filter(array_map('sanitize_text_field', $terms))),
            ];
        } else {
            $attributes_out[] = [
                'label' => sanitize_text_field($attr->get_name()),
                'values' => array_values(array_filter(array_map('sanitize_text_field', $attr->get_options()))),
            ];
        }
    }

    $sku = (string) $product->get_sku();
    $price = $product->get_price();
    $regular = $product->get_regular_price();
    $sale = $product->get_sale_price();

    return [
        'title' => sanitize_text_field($product->get_name()),
        'sku' => sanitize_text_field($sku),
        'categories' => array_values(array_filter(array_map('sanitize_text_field', (array) $cats))),
        'tags' => array_values(array_filter(array_map('sanitize_text_field', (array) $tags))),
        'attributes' => $attributes_out,
        'price' => $price !== '' ? (string) $price : '',
        'regular_price' => $regular !== '' ? (string) $regular : '',
        'sale_price' => $sale !== '' ? (string) $sale : '',
        'short_description_existing' => wp_strip_all_tags((string) $product->get_short_description()),
        'description_existing' => wp_strip_all_tags((string) $product->get_description()),
    ];
}

function bpcab_build_prompt(array $payload, string $notes = ''): string {
    $attrs_lines = [];
    foreach (($payload['attributes'] ?? []) as $attr) {
        $label = $attr['label'] ?? '';
        $values = $attr['values'] ?? [];
        if (!$label || empty($values)) {
            continue;
        }
        $attrs_lines[] = '- ' . $label . ' : ' . implode(', ', $values);
    }

    $notes_clean = trim(wp_strip_all_tags($notes));

    // Prompt orienté e-commerce: bénéfices, usage, contraintes, pas de promesses non vérifiées.
    $prompt = "Vous êtes un rédacteur e-commerce senior. Rédigez pour WooCommerce une description produit en FR.nn";
    $prompt .= "Règles STRICTES:n";
    $prompt .= "1) N'inventez aucune caractéristique non fournie.n";
    $prompt .= "2) Pas de superlatifs gratuits ("le meilleur", "incroyable").n";
    $prompt .= "3) Style clair, concret, orienté bénéfices et usages.n";
    $prompt .= "4) HTML autorisé: <p>, <ul>, <li>, <strong>, <em>. Pas de titres H1/H2.n";
    $prompt .= "5) Retournez STRICTEMENT un JSON valide avec les clés: long_html, short_html.nn";

    $prompt .= "Données produit:n";
    $prompt .= "- Titre: " . ($payload['title'] ?? '') . "n";
    if (!empty($payload['sku'])) {
        $prompt .= "- SKU: " . $payload['sku'] . "n";
    }
    if (!empty($payload['categories'])) {
        $prompt .= "- Catégories: " . implode(', ', (array) $payload['categories']) . "n";
    }
    if (!empty($payload['tags'])) {
        $prompt .= "- Tags: " . implode(', ', (array) $payload['tags']) . "n";
    }
    if (!empty($attrs_lines)) {
        $prompt .= "- Attributs:n" . implode("n", $attrs_lines) . "n";
    }

    // Le prix est optionnel: utile si vous voulez positionnement "entrée de gamme/premium".
    if (!empty($payload['price'])) {
        $prompt .= "- Prix actuel (indicatif): " . $payload['price'] . "n";
    }

    if ($notes_clean !== '') {
        $prompt .= "nNotes internes:n" . $notes_clean . "n";
    }

    // Si une description existe déjà, on peut demander une amélioration plutôt qu'une réécriture totale.
    $existing = trim((string) ($payload['description_existing'] ?? ''));
    if ($existing !== '' && mb_strlen($existing) > 80) {
        $prompt .= "nTexte existant (à améliorer sans changer le sens):n" . $existing . "n";
    }

    $prompt .= "nFormat attendu (exemple):n";
    $prompt .= "{"long_html":"<p>...</p>","short_html":"<p>...</p>"}n";

    return $prompt;
}

5) Rufen Sie die KI-API über wp_remote_post() auf.

Beispiel mit der OpenAI „Responses“-API. Bei Verwendung eines anderen Anbieters (Anthropic, Mistral, Google) ändert sich die Struktur, die Prinzipien bleiben jedoch gleich: Timeout, Fehlerbehandlung, striktes Parsen.

Ich erzwinge eine JSON-Antwort in der Eingabeaufforderung und parse sie anschließend serverseitig. Dadurch werden 80 % der ausführlichen Antworten vermieden, die das Einfügen verhindern.

function bpcab_call_openai_responses_api(string $prompt) {
    $endpoint = 'https://api.openai.com/v1/responses';

    $body = [
        // Modèle: choisissez un modèle "mini" pour réduire les coûts si la qualité vous suffit.
        // Adaptez selon votre compte et les modèles disponibles.
        'model' => 'gpt-4.1-mini',
        'input' => [
            [
                'role' => 'user',
                'content' => [
                    [
                        'type' => 'input_text',
                        'text' => $prompt,
                    ],
                ],
            ],
        ],
        // Limite raisonnable: descriptions produit, pas un roman.
        'max_output_tokens' => 700,
        'temperature' => 0.6,
    ];

    $args = [
        'timeout' => 25, // Évitez 60s: en admin, ça se ressent vite.
        'headers' => [
            'Content-Type' => 'application/json',
            'Authorization' => 'Bearer ' . BPCAB_OPENAI_API_KEY,
        ],
        'body' => wp_json_encode($body),
    ];

    $res = wp_remote_post($endpoint, $args);

    if (is_wp_error($res)) {
        return new WP_Error('http_error', 'Erreur HTTP vers l’API IA: ' . $res->get_error_message());
    }

    $code = (int) wp_remote_retrieve_response_code($res);
    $raw = (string) wp_remote_retrieve_body($res);

    if ($code < 200 || $code >= 300) {
        // Log minimal (évitez de logger des données sensibles).
        error_log('[BPCAB AI] API non-200: ' . $code . ' body=' . substr($raw, 0, 500));
        return new WP_Error('api_non_200', 'Réponse IA invalide (HTTP ' . $code . '). Vérifiez votre clé/quota.');
    }

    $json = json_decode($raw, true);
    if (!is_array($json)) {
        return new WP_Error('json_decode', 'Réponse IA non JSON (json_decode a échoué).');
    }

    // Récupération du texte: selon l’API, la sortie peut être structurée.
    // On essaie plusieurs chemins connus, puis on échoue proprement.
    $text = '';

    // Chemin courant: output_text agrégé
    if (isset($json['output_text']) && is_string($json['output_text'])) {
        $text = $json['output_text'];
    }

    // Fallback: certaines réponses contiennent output[] avec content[]
    if ($text === '' && !empty($json['output']) && is_array($json['output'])) {
        foreach ($json['output'] as $item) {
            if (!is_array($item) || empty($item['content']) || !is_array($item['content'])) {
                continue;
            }
            foreach ($item['content'] as $c) {
                if (is_array($c) && ($c['type'] ?? '') === 'output_text' && isset($c['text'])) {
                    $text .= (string) $c['text'];
                }
            }
        }
    }

    $text = trim($text);
    if ($text === '') {
        error_log('[BPCAB AI] Sortie vide. Raw=' . substr($raw, 0, 500));
        return new WP_Error('empty_output', 'L’API IA a renvoyé une sortie vide.');
    }

    // On attend un JSON strict dans $text
    $out = json_decode($text, true);
    if (!is_array($out)) {
        error_log('[BPCAB AI] JSON attendu mais non parsable. Text=' . substr($text, 0, 500));
        return new WP_Error('bad_format', 'Format IA inattendu. Ajustez le prompt (JSON strict).');
    }

    $long = isset($out['long_html']) ? (string) $out['long_html'] : '';
    $short = isset($out['short_html']) ? (string) $out['short_html'] : '';

    return [
        'long' => $long,
        'short' => $short,
    ];
}

6) Produkt aktualisieren (Inhalt + Auszug)

Der zugrundeliegende WordPress-Beitrag wird aktualisiert. WooCommerce liest anschließend diese Felder, um die Beschreibung anzuzeigen. Ich verwende wp_update_post() um im Standard-WordPress-Workflow zu bleiben (Hooks, Revisionen, falls aktiviert, usw.).

function bpcab_update_product_descriptions(int $product_id, string $long_html, string $short_html) {
    $update = [
        'ID' => $product_id,
        'post_content' => $long_html,
        'post_excerpt' => $short_html,
    ];

    $res = wp_update_post(wp_slash($update), true, false);

    if (is_wp_error($res)) {
        return new WP_Error('update_failed', 'Échec mise à jour produit: ' . $res->get_error_message());
    }

    // Optionnel: forcer une mise à jour du produit WooCommerce (index, lookup tables, etc.)
    if (function_exists('wc_get_product')) {
        $product = wc_get_product($product_id);
        if ($product) {
            $product->save();
        }
    }

    return true;
}

Der vollständige kompilierte Code

Kopieren Sie diese Datei und fügen Sie sie ein in wp-content/mu-plugins/bpcab-ai-woo-descriptions.phpErstellen Sie außerdem das JS. bpcab-ai-woo-admin.js daneben (siehe oben).

<?php
/**
 * Plugin Name: BPCAB - IA descriptions produits WooCommerce
 * Description: Génère des descriptions produits via IA depuis l'admin WooCommerce (WP 6.9.4+, PHP 8.1+).
 * Version: 1.0.0
 */

if (!defined('ABSPATH')) {
    exit;
}

/**
 * Meta box sur l'écran produit.
 */
add_action('add_meta_boxes', function () {
    add_meta_box(
        'bpcab_ai_product_desc',
        'Descriptions IA',
        'bpcab_render_ai_metabox',
        'product',
        'side',
        'high'
    );
});

function bpcab_render_ai_metabox(WP_Post $post): void {
    $product_id = (int) $post->ID;

    if (!current_user_can('edit_product', $product_id)) {
        echo '<p>Vous n’avez pas les droits pour modifier ce produit.</p>';
        return;
    }

    wp_nonce_field('bpcab_ai_generate_desc', 'bpcab_ai_nonce');

    echo '<p>
        <label for="bpcab_ai_notes"><strong>Notes internes (optionnel)</strong></label>
        <textarea id="bpcab_ai_notes" style="width:100%;min-height:70px;" placeholder="Ex: ton premium, mentionner la garantie 2 ans, éviter les superlatifs..."></textarea>
    </p>';

    echo '<p>
        <button type="button" class="button button-primary" id="bpcab-ai-generate" data-product-id="' . esc_attr((string) $product_id) . '">
            Générer avec IA
        </button>
    </p>';

    echo '<div id="bpcab-ai-status" style="margin-top:8px;"></div>';
}

/**
 * JS admin (uniquement sur l'écran produit).
 */
add_action('admin_enqueue_scripts', function (string $hook_suffix) {
    if (!in_array($hook_suffix, ['post.php', 'post-new.php'], true)) {
        return;
    }

    $screen = get_current_screen();
    if (!$screen || $screen->post_type !== 'product') {
        return;
    }

    wp_enqueue_script(
        'bpcab-ai-woo-admin',
        plugins_url('bpcab-ai-woo-admin.js', __FILE__),
        ['jquery'],
        '1.0.0',
        true
    );

    wp_localize_script('bpcab-ai-woo-admin', 'BPCAB_AI', [
        'ajaxUrl' => admin_url('admin-ajax.php'),
        'nonce'   => wp_create_nonce('bpcab_ai_generate_desc'),
    ]);
});

/**
 * Endpoint AJAX sécurisé.
 */
add_action('wp_ajax_bpcab_ai_generate_product_desc', function () {
    $nonce = isset($_POST['nonce']) ? sanitize_text_field((string) $_POST['nonce']) : '';
    if (!wp_verify_nonce($nonce, 'bpcab_ai_generate_desc')) {
        wp_send_json_error(['message' => 'Nonce invalide. Rechargez la page produit.'], 403);
    }

    $product_id = isset($_POST['product_id']) ? (int) $_POST['product_id'] : 0;
    if ($product_id <= 0) {
        wp_send_json_error(['message' => 'ID produit invalide.'], 400);
    }

    if (!current_user_can('edit_product', $product_id)) {
        wp_send_json_error(['message' => 'Droits insuffisants.'], 403);
    }

    if (!defined('BPCAB_OPENAI_API_KEY') || !BPCAB_OPENAI_API_KEY) {
        wp_send_json_error(['message' => 'Clé API manquante. Définissez BPCAB_OPENAI_API_KEY dans wp-config.php.'], 500);
    }

    $notes = isset($_POST['notes']) ? wp_kses_post((string) $_POST['notes']) : '';

    $result = bpcab_ai_generate_and_update_product($product_id, $notes);

    if (is_wp_error($result)) {
        wp_send_json_error([
            'message' => $result->get_error_message(),
            'code'    => $result->get_error_code(),
        ], 500);
    }

    wp_send_json_success(['updated' => true]);
});

function bpcab_ai_generate_and_update_product(int $product_id, string $notes = '') {
    if (!function_exists('wc_get_product')) {
        return new WP_Error('woocommerce_missing', 'WooCommerce ne semble pas actif.');
    }

    $product = wc_get_product($product_id);
    if (!$product) {
        return new WP_Error('product_missing', 'Produit introuvable.');
    }

    $user_id = get_current_user_id();
    $rl_key = 'bpcab_ai_rl_' . $user_id . '_' . $product_id;
    if (get_transient($rl_key)) {
        return new WP_Error('rate_limited', 'Vous générez trop vite. Attendez 30 secondes et réessayez.');
    }
    set_transient($rl_key, 1, 30);

    $payload = bpcab_build_product_payload_for_prompt($product);

    $fingerprint = hash('sha256', wp_json_encode([
        'payload' => $payload,
        'notes'   => $notes,
        'v'       => '1.0.0',
    ]));

    $cache_key = 'bpcab_ai_desc_' . $product_id . '_' . substr($fingerprint, 0, 12);
    $cached = get_transient($cache_key);
    if (is_array($cached) && isset($cached['long'], $cached['short'])) {
        return bpcab_update_product_descriptions($product_id, $cached['long'], $cached['short']);
    }

    $prompt = bpcab_build_prompt($payload, $notes);

    $ai = bpcab_call_openai_responses_api($prompt);
    if (is_wp_error($ai)) {
        return $ai;
    }

    $long = wp_kses_post($ai['long'] ?? '');
    $short = wp_kses_post($ai['short'] ?? '');

    if (mb_strlen(wp_strip_all_tags($long)) < 80) {
        return new WP_Error('ai_empty', 'La réponse IA est trop courte ou vide. Vérifiez vos données produit et le prompt.');
    }
    if (mb_strlen(wp_strip_all_tags($short)) < 30) {
        $short = wp_trim_words(wp_strip_all_tags($long), 35, '…');
    }

    set_transient($cache_key, ['long' => $long, 'short' => $short], 30 * DAY_IN_SECONDS);

    return bpcab_update_product_descriptions($product_id, $long, $short);
}

function bpcab_build_product_payload_for_prompt(WC_Product $product): array {
    $product_id = $product->get_id();

    $cats = wp_get_post_terms($product_id, 'product_cat', ['fields' => 'names']);
    $tags = wp_get_post_terms($product_id, 'product_tag', ['fields' => 'names']);

    $attributes_out = [];
    foreach ($product->get_attributes() as $attr) {
        if ($attr->is_taxonomy()) {
            $taxonomy = $attr->get_name();
            $label = wc_attribute_label($taxonomy);
            $terms = wp_get_post_terms($product_id, $taxonomy, ['fields' => 'names']);
            $attributes_out[] = [
                'label' => $label,
                'values' => array_values(array_filter(array_map('sanitize_text_field', $terms))),
            ];
        } else {
            $attributes_out[] = [
                'label' => sanitize_text_field($attr->get_name()),
                'values' => array_values(array_filter(array_map('sanitize_text_field', $attr->get_options()))),
            ];
        }
    }

    $sku = (string) $product->get_sku();
    $price = $product->get_price();
    $regular = $product->get_regular_price();
    $sale = $product->get_sale_price();

    return [
        'title' => sanitize_text_field($product->get_name()),
        'sku' => sanitize_text_field($sku),
        'categories' => array_values(array_filter(array_map('sanitize_text_field', (array) $cats))),
        'tags' => array_values(array_filter(array_map('sanitize_text_field', (array) $tags))),
        'attributes' => $attributes_out,
        'price' => $price !== '' ? (string) $price : '',
        'regular_price' => $regular !== '' ? (string) $regular : '',
        'sale_price' => $sale !== '' ? (string) $sale : '',
        'short_description_existing' => wp_strip_all_tags((string) $product->get_short_description()),
        'description_existing' => wp_strip_all_tags((string) $product->get_description()),
    ];
}

function bpcab_build_prompt(array $payload, string $notes = ''): string {
    $attrs_lines = [];
    foreach (($payload['attributes'] ?? []) as $attr) {
        $label = $attr['label'] ?? '';
        $values = $attr['values'] ?? [];
        if (!$label || empty($values)) {
            continue;
        }
        $attrs_lines[] = '- ' . $label . ' : ' . implode(', ', $values);
    }

    $notes_clean = trim(wp_strip_all_tags($notes));

    $prompt = "Vous êtes un rédacteur e-commerce senior. Rédigez pour WooCommerce une description produit en FR.nn";
    $prompt .= "Règles STRICTES:n";
    $prompt .= "1) N'inventez aucune caractéristique non fournie.n";
    $prompt .= "2) Pas de superlatifs gratuits ("le meilleur", "incroyable").n";
    $prompt .= "3) Style clair, concret, orienté bénéfices et usages.n";
    $prompt .= "4) HTML autorisé: <p>, <ul>, <li>, <strong>, <em>. Pas de titres H1/H2.n";
    $prompt .= "5) Retournez STRICTEMENT un JSON valide avec les clés: long_html, short_html.nn";

    $prompt .= "Données produit:n";
    $prompt .= "- Titre: " . ($payload['title'] ?? '') . "n";
    if (!empty($payload['sku'])) {
        $prompt .= "- SKU: " . $payload['sku'] . "n";
    }
    if (!empty($payload['categories'])) {
        $prompt .= "- Catégories: " . implode(', ', (array) $payload['categories']) . "n";
    }
    if (!empty($payload['tags'])) {
        $prompt .= "- Tags: " . implode(', ', (array) $payload['tags']) . "n";
    }
    if (!empty($attrs_lines)) {
        $prompt .= "- Attributs:n" . implode("n", $attrs_lines) . "n";
    }
    if (!empty($payload['price'])) {
        $prompt .= "- Prix actuel (indicatif): " . $payload['price'] . "n";
    }

    if ($notes_clean !== '') {
        $prompt .= "nNotes internes:n" . $notes_clean . "n";
    }

    $existing = trim((string) ($payload['description_existing'] ?? ''));
    if ($existing !== '' && mb_strlen($existing) > 80) {
        $prompt .= "nTexte existant (à améliorer sans changer le sens):n" . $existing . "n";
    }

    $prompt .= "nFormat attendu (exemple):n";
    $prompt .= "{"long_html":"<p>...</p>","short_html":"<p>...</p>"}n";

    return $prompt;
}

function bpcab_call_openai_responses_api(string $prompt) {
    $endpoint = 'https://api.openai.com/v1/responses';

    $body = [
        'model' => 'gpt-4.1-mini',
        'input' => [
            [
                'role' => 'user',
                'content' => [
                    [
                        'type' => 'input_text',
                        'text' => $prompt,
                    ],
                ],
            ],
        ],
        'max_output_tokens' => 700,
        'temperature' => 0.6,
    ];

    $args = [
        'timeout' => 25,
        'headers' => [
            'Content-Type' => 'application/json',
            'Authorization' => 'Bearer ' . BPCAB_OPENAI_API_KEY,
        ],
        'body' => wp_json_encode($body),
    ];

    $res = wp_remote_post($endpoint, $args);

    if (is_wp_error($res)) {
        return new WP_Error('http_error', 'Erreur HTTP vers l’API IA: ' . $res->get_error_message());
    }

    $code = (int) wp_remote_retrieve_response_code($res);
    $raw = (string) wp_remote_retrieve_body($res);

    if ($code < 200 || $code >= 300) {
        error_log('[BPCAB AI] API non-200: ' . $code . ' body=' . substr($raw, 0, 500));
        return new WP_Error('api_non_200', 'Réponse IA invalide (HTTP ' . $code . '). Vérifiez votre clé/quota.');
    }

    $json = json_decode($raw, true);
    if (!is_array($json)) {
        return new WP_Error('json_decode', 'Réponse IA non JSON (json_decode a échoué).');
    }

    $text = '';
    if (isset($json['output_text']) && is_string($json['output_text'])) {
        $text = $json['output_text'];
    }

    if ($text === '' && !empty($json['output']) && is_array($json['output'])) {
        foreach ($json['output'] as $item) {
            if (!is_array($item) || empty($item['content']) || !is_array($item['content'])) {
                continue;
            }
            foreach ($item['content'] as $c) {
                if (is_array($c) && ($c['type'] ?? '') === 'output_text' && isset($c['text'])) {
                    $text .= (string) $c['text'];
                }
            }
        }
    }

    $text = trim($text);
    if ($text === '') {
        error_log('[BPCAB AI] Sortie vide. Raw=' . substr($raw, 0, 500));
        return new WP_Error('empty_output', 'L’API IA a renvoyé une sortie vide.');
    }

    $out = json_decode($text, true);
    if (!is_array($out)) {
        error_log('[BPCAB AI] JSON attendu mais non parsable. Text=' . substr($text, 0, 500));
        return new WP_Error('bad_format', 'Format IA inattendu. Ajustez le prompt (JSON strict).');
    }

    return [
        'long' => isset($out['long_html']) ? (string) $out['long_html'] : '',
        'short' => isset($out['short_html']) ? (string) $out['short_html'] : '',
    ];
}

function bpcab_update_product_descriptions(int $product_id, string $long_html, string $short_html) {
    $update = [
        'ID' => $product_id,
        'post_content' => $long_html,
        'post_excerpt' => $short_html,
    ];

    $res = wp_update_post(wp_slash($update), true, false);

    if (is_wp_error($res)) {
        return new WP_Error('update_failed', 'Échec mise à jour produit: ' . $res->get_error_message());
    }

    if (function_exists('wc_get_product')) {
        $product = wc_get_product($product_id);
        if ($product) {
            $product->save();
        }
    }

    return true;
}

Code-Erklärung

Warum ein Button (AJAX) anstelle eines automatischen Hooks?

In WooCommerce kann ein Produkt mehrfach gespeichert werden, ohne dass Sie auf „Aktualisieren“ klicken müssen: automatisches Speichern, Revisionen, Metadaten-Aktualisierungen von anderen Plugins, Bestandssynchronisierung usw. Wenn Sie die KI einbinden save_post_product Ohne entsprechende Schutzmaßnahmen werden Sie Phantom-API-Aufrufe auslösen.

Der Button erzwingt eine bewusste Aktion. Und das vereinfacht die Fehlersuche: Man klickt, sieht den Status und behebt das Problem.

Warum ein Hash-basierter Transient?

Ein einfacher „pro Produkt“-Cache (z. B.: bpcab_ai_desc_123Dies ist schnell irreführend, da sich das Produkt ändert. Der SHA-256-Hash der Nutzdaten + Notizen garantiert: gleiche Eingaben → gleiche Ausgabe → keine Rückzahlung.

Ein echter Sonderfall: Wenn Sie die Eingabeaufforderung (Struktur, Regeln) ändern, möchten Sie den Cache ungültig machen. Daher das kleine Feld v im Hash.

Warum wp_kses_post() und nicht sanitize_text_field()?

sanitize_text_field() zerstört den HTML-Code. Eine WooCommerce-Beschreibung benötigt jedoch Listen, Fettdruck und Absätze. In der Praxis wp_kses_post() ist der richtige Kompromiss: HTML ist für einen Beitrag erlaubt, Skripte werden entfernt.

Ich sehe immer noch Seiten, die die "rohe" KI-Antwort einfügen in post_contentWenn die KI einen verdächtigen Link oder ungewöhnlichen HTML-Code zurückgibt, haben Sie unnötigerweise eine Sicherheitslücke geöffnet. Es handelt sich zwar nicht um einen automatischen XSS-Angriff (WordPress filtert solche Angriffe bereits an einigen Stellen), aber Sie machen sich die Sache unnötig schwer.

Warum eine kurze Auszeit?

Im Administrationsbereich blockiert ein Timeout von 60 Sekunden die Benutzeroberfläche und lässt WordPress scheinbar einfrieren. 20–30 Sekunden sind ein guter Maximalwert. Bei sehr komplexen Produkten empfiehlt sich die Verwendung asynchroner Generierung (Cronjob/Warteschlange) oder die Nutzung erweiterter Optionen.

API-Kosten und Optimierung

Die Kosten hängen vom Modell und der Anzahl der Tokens ab. Eine typische Produktseite (Eingabeaufforderung + Antwort) kostet etwa 800 bis 2000 Token Die Kosten hängen von der Anzahl der Attribute und der gewünschten Länge ab. Bei einem „Mini“-Modell sind die Kosten pro Generation oft sehr niedrig, im großen Maßstab spielen sie jedoch eine Rolle.

Einfache Schätzung (muss an Ihr Modell angepasst werden)

  • Nehmen wir an, es werden 1200 Token pro Produkt benötigt (Input + Output).
  • 1000 Produkte/Monat → 1,2 Mio. Token/Monat.
  • Bei einem „Mini“-Modell ist der Preis in der Regel angemessen, aber wenn Sie regenerieren (Ohne Zwischenspeicherung) multipliziert man schnell mit 3 oder 10.

Optimierungen, die tatsächlich funktionieren

  • Cache per Fingerabdruck (bereits vorhanden): Es bietet den besten ROI.
  • Begrenzen Sie die Ausgabe mit max_output_tokens : vermeidet lange Antworten.
  • Kleineres Modell Für größere Mengen ist ein robusteres Modell nur für Premiumprodukte geeignet.
  • Stapel (Erweiterte Variante): 50 Produkte werden nachts per Cronjob verarbeitet, mit einem täglichen Budget.
  • Reduziere die Eingabeaufforderung : Senden Sie keine leeren Attribute und auch nicht die vorhandene Beschreibung, falls diese leer ist.

Erweiterte Varianten und Anwendungsfälle

Option 1: Nur die Kurzbeschreibung (Auszug) generieren

Nützlich, wenn Ihre lange Beschreibung bereits verfasst (oder von einem Hersteller bereitgestellt) ist, Sie aber einen verkaufsfreundlichen und einheitlichen Auszug für Kategorieseiten benötigen.

  • Ändern Sie die Eingabeaufforderung so, dass sie nur noch nach Folgendem fragt: short_html.
  • Tanz bpcab_update_product_descriptions()nur aktualisieren post_excerpt.

Variante 2: Asynchrone Generierung (Vermeidung von Timeouts)

Auf langsamen Hosting-Servern kann der externe Aufruf länger als 25 Sekunden dauern. In diesem Fall sollte eine verzögerte Aufgabe ausgelöst werden:

  • AJAX: registriert eine „Anfrage“ (Post-Metadaten) und antwortet sofort.
  • Ein Cronjob (WP-Cron oder Cronserver) verarbeitet Anfragen in einer Warteschlange, 5 pro Minute.

Ich füge hier nicht den gesamten Code ein, damit er leicht kopierbar bleibt, aber der Kernpunkt ist… Führen Sie den KI-Aufruf nicht in der Administratoranfrage durch. wenn Ihre Infrastruktur instabil ist.

Variante 3: Kompatibilität mit Divi 5 / Elementor / Avada

Die Generierung erfolgt auf der WooCommerce-Seite, sodass die Kompatibilität erhalten bleibt. Interessant wird es erst, wenn Sie mithilfe Ihres Seiten-Builders ein „Badge“ oder einen „Highlights“-Block auf der Produktseite anzeigen.

  • Divi 5 Sie können ein Modul erstellen, das liest post_excerpt oder ein KI-generiertes „Stärken“-Metafeld. Wenn Sie auf post_content/post_excerptDivi hat nichts Besonderes zu tun.
  • Elementor Verwenden Sie das Widget „Produktkurzbeschreibung“, dann wird es angezeigt. automatiquement Der aktualisierte Auszug.
  • Avada Die WooCommerce-Komponente „Produktinhalt“ / „Produktauszug“ spiegelt diese Felder ohne Anpassung wider.

Ein Tipp, den ich oft anwende: die KI nach einer Liste fragen. <ul> Fügen Sie die Vorteile in die ausführliche Beschreibung ein und gestalten Sie diese Liste anschließend mithilfe des Themes/Builders. So erzielen Sie ein einheitliches Erscheinungsbild ohne zusätzlichen Programmieraufwand.

Sicherheit und bewährte Verfahren

Geben Sie den API-Schlüssel niemals clientseitig preis.

Der JS-Admin ruft gerade auf admin-ajax.phpDer Schlüssel bleibt in wp-config.phpWenn Sie den Schlüssel in ein Skript einfügen, selbst im Admin-Panel, wird er am Ende irgendwohin kopiert (Cache, Browsererweiterungen, Proxy).

Validieren und begrenzen

  • Leistungsportfolio : edit_product Mindestens. Lassen Sie nicht zu, dass eine unkontrollierte Rolle "shop_manager" 10.000 Generationen auslöst.
  • Nonce : Es sind bereits Mechanismen vorhanden, um CSRF-Anfragen zu verhindern.
  • Ratenbegrenzung 30 Sekunden pro Benutzer und Produkt. Optional kann ein Tageskontingent (Zähler für die Anzahl der Übergänge) hinzugefügt werden.

Bereinigung der KI-Antwort

wp_kses_post() ist ein Minimum. Wenn Sie strengere Vorgaben machen möchten, können Sie eine Liste zulässiger Tags übergeben. wp_kses() und lehnen Sie jegliche externen Links ab.

DSGVO / Datenübermittlung

Bitte senden Sie keine personenbezogenen Daten (Name, Adresse usw.). Wir übermitteln hier ausschließlich Produktdaten. Wenn Sie die Beschreibungen für einen Nutzer personalisieren möchten, bewegen Sie sich in einem sensibleren DSGVO-Kontext (Rechtsgrundlage, Unterauftragnehmer, Datenschutzbeauftragter usw.).

Realistische Fehler, die man vermeiden sollte

  • Kopieren Sie den Code an die falsche Stelle : ein eingefügter Ausschnitt functions.php Das übergeordnete Theme geht beim nächsten Update verloren.
  • Vergiss das Semikolon. Ein einzelner PHP-Fehler führt zu einem weißen Bildschirm im Adminbereich. Test auf der Staging-Umgebung.
  • Unangemessener Haken : auslösen bei init ou save_post Ohne Schutzmechanismen → unbeabsichtigte KI-Aufrufe.
  • Test im Produktivbetrieb ohne Backup Sie können bestehende Beschreibungen in großen Mengen überschreiben.
  • PHP ist zu alt Einige der oben genannten Syntax-/Typisierungen setzen PHP 8.1+ voraus. Unter PHP 7.4 funktioniert dies nicht.

Wie man testet und Fehler behebt

1) Aktivieren Sie die Protokollierung ordnungsgemäß.

Tanz wp-config.php (Beim Staging aktivieren):

define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);

Sie werden dann lesen wp-content/debug.logOffizielle Dokumentation: Debuggen in WordPress.

2) Zuerst an einem „einfachen“ Produkt testen.

  • Ein klarer Titel
  • 2–3 Attribute ausgefüllt
  • Eine Kategorie

Wenn es funktioniert, wechseln Sie zu einem variablen Produkt mit vielen Attributen. Zu lange Eingabeaufforderungen erhöhen das Risiko einer fehlerhaften (oder abgeschnittenen) Ausgabe.

3) Überprüfen Sie die AJAX-Anfrage

  • Öffnen Sie den DevTools-Browser → Netzwerk → admin-ajax.php.
  • Schauen Sie sich den HTTP-Code (200/403/500) und das zurückgegebene JSON an.

4) Überprüfen Sie die API-Antwort

Falls JSON-Formatfehler auftreten, protokollieren Sie nur Ausschnitte (bereits erledigt mit substr()Protokollieren Sie nicht die gesamte Eingabeaufforderung, wenn Sie darin sensible Informationen eingeben.

Wenn es nicht funktioniert

Hier ist ein Diagnosediagramm, das ich bei der Fehlersuche in dieser Art von Integration häufig verwende.

Symptom Mögliche Ursache Überprüfung Lösung
Der „Generieren“-Button hat keine Funktion. JavaScript wurde auf dem Produktbildschirm nicht geladen. DevTools-Konsole/Netzwerk, Fehlen von bpcab-ai-woo-admin.js Scheck admin_enqueue_scriptsder Weg plugins_url()und dass die JS-Datei existiert
Fehler 403 „Ungültige Nonce“ Benachrichtigung nicht gesendet oder Seite zu alt Netzwerk → POST-Nutzlast enthält nonce ? Produktseite neu laden, prüfen wp_localize_script und das Vorgehen des Nuntius
Fehler 500 „Fehlender API-Schlüssel“ Konstante fehlt / leer Scheck wp-config.php und die Umwelt Definieren BPCAB_OPENAI_API_KEYLeeren Sie den Opcache gegebenenfalls.
HTTP 401/403 auf der Seite der KI-API Ungültiger Schlüssel, nicht autorisiertes Projekt Protokoll: „API non-200“ + Code Generieren Sie einen neuen Schlüssel und überprüfen Sie die Projektberechtigungen beim Anbieter.
HTTP 429 Quote überschritten / Lieferantenpreislimit Protokolle + Lieferanten-Dashboard Fügen Sie eine Backoff-/Retry-Funktionalität hinzu, reduzieren Sie die Taktfrequenz, aktivieren Sie das Caching und verwenden Sie ein leichteres Modell.
„Unerwartetes KI-Format“ Die KI gibt Nicht-JSON-Text zurück. Teilprotokoll der Ausgabe Die Aufforderung strenger gestalten, senken temperatureVerringern Sie die angeforderte Länge
Beschreibungen werden „zufällig“ zerkleinert Test in der Produktionsumgebung + mehrere Klicks + automatisches Speichern Revisionsverlauf / Protokolle Arbeiten Sie an der Staging-Umgebung, fügen Sie eine längere Sperre (vorübergehend) hinzu, fordern Sie eine Bestätigung an

Zwei häufige Fallstricke

  • Code-Snippet durch ein Snippet-Plugin beschädigt Bei Verwendung eines Plugins vom Typ „Code-Snippets“ kann ein Syntaxfehler das Snippet deaktivieren und die Admin-Oberfläche in einen instabilen Zustand versetzen. Das mu-Plugin ist für diese Art von Code stabiler.
  • Verborgener Konflikt Manche Websites verwenden einen aggressiven persistenten Objektcache. Transiente Objekte können bei Fehlkonfiguration zwischen Umgebungen geteilt werden. Falls die Beschreibungen nicht übereinstimmen, deaktivieren Sie zunächst vorübergehend den Objektcache in der Staging-Umgebung.

Ressourcen

FAQ

Funktioniert es auch mit variablen Produkten (Variationen)?

Ja, aber der obige Code generiert die Beschreibung des MutterproduktWenn Sie für jede Variante einen Text benötigen, müssen Sie die Varianten durchlaufen und in separaten Meta-Tags speichern (und die Darstellung anpassen). Ich mache das selten: Es ist aufwendig und aus SEO-Sicht oft unnötig.

Kann ich verhindern, dass die KI den Preis erwähnt?

Ja: Feld entfernen price der Nutzdaten und der entsprechenden Zeile in der Eingabeaufforderung. Ich empfehle, keine Preise anzugeben, wenn Sie häufig Aktionen durchführen, da der Text sonst veraltet.

Warum eine JSON-Ausgabe anstelle von einfachem HTML anfordern?

Weil Sie zwei Felder (lang + kurz) und eine zuverlässige Analyse benötigen. JSON reduziert Fälle, in denen die KI fehlerhafte Sätze hinzufügt. Und wenn ein Fehler auftritt, wird dieser sofort angezeigt (eine „bad_format“-Fehlermeldung).

Kann ich stattdessen Anthropic, Mistral oder Google verwenden?

Ja. Die Architektur soll beibehalten werden (AJAX-Admin → wp_remote_post() → parsen → wp_kses_post() → Aktualisierung). Nur der Endpunkt und das JSON-Format ändern sich. Wenn Sie mir den genauen Provider nennen, kann ich Ihnen eine Funktion bereitstellen. bpcab_call_* äquivalent.

Besteht die Gefahr, dass dadurch doppelte Inhalte entstehen?

Sind Ihre Produkte sehr ähnlich (gleiche Eigenschaften, nahezu identische Titel), kann die KI ähnliche Texte generieren. Um dies zu minimieren, fügen Sie der Eingabeaufforderung die Einschränkung „Unterscheidung nach Verwendungszweck“ hinzu und ergänzen Sie sie um ein Alleinstellungsmerkmal (Marke, Material, Hauptvorteil). Bei homogenen Produktkatalogen ist eine gewisse Ähnlichkeit jedoch unvermeidbar.

Wie können wir verhindern, dass KI Funktionen erfindet?

Ganz vermeiden lässt es sich nie. Das Risiko lässt sich jedoch erheblich reduzieren durch:

  • Die Erfindung wurde ausdrücklich verboten (bereits geschehen).
  • Senken temperature (0.3-0.6).
  • Bereitstellung ausschließlich strukturierter Daten (Attribute) und Vermeidung mehrdeutiger „Notizen“.

Ich erhalte die Fehlermeldung „Unerwartetes KI-Format“. Was soll ich als Erstes tun?

Komplexität reduzieren: weniger Attribute, max_output_tokens Weiter oben (falls abgeschnitten wird) und mit einer noch strengeren Eingabeaufforderung („Nur JSON zurückgeben, kein Text davor/danach“). Erfahrungsgemäß ist die Ausgabe in neun von zehn Fällen einfach nur nicht-JSON.

Warum steht auf dem Knopf „OK“, aber ich kann auf der Vorderseite nichts sehen?

Oftmals liefert der Seiten- oder Objektcache eine ältere Version aus. Leeren Sie den Cache und prüfen Sie, ob Ihr Theme korrekt angezeigt wird. the_content() und dem Standard-WooCommerce-Code-Snippet. Bei Elementor/Avada/Divi stellen Sie sicher, dass Sie nicht stattdessen ein benutzerdefiniertes Feld anzeigen.

Kann ich die Beschreibung vor dem Überschreiben in der Vorschau ansehen?

Ja: anstatt anzurufen wp_update_post(), zurücksenden long_html et short_html In der AJAX-Antwort werden die Daten in einem Modal angezeigt. Anschließend wird ein zweiter „Anwenden“-Button hinzugefügt. Diese Version setze ich in Shops ein, in denen mehrere Personen Artikel bearbeiten.

Könnte die Generierung einer großen Menge davon meine SEO beeinträchtigen?

Wenn Sie 500 generierte Produktblätter ohne Korrekturlesen veröffentlichen, gehen Sie ein Qualitätsrisiko ein (und gefährden damit Ihre Suchmaschinenoptimierung). Der optimale Workflow sieht so aus: KI-Generierung → menschliche Prüfung → Veröffentlichung. Strategische Produkte sollten Sie sogar komplett überarbeiten.