この記事では、OpenAI の GPT-4 言語モデルを使用してブログの投稿を自動翻訳するために作成された Proof of Concept (POC) 用の Python スクリプトを共有します。このスクリプトは、Hugo ブログ構造内の Markdown ファイルを処理するために特別に設計されており、記事の多言語管理を容易にします。これらの記事は 英語スペイン語中国語 で利用可能です。

プロジェクトの開始: AI と自動化をブログに融合する

このブログ記事の翻訳を自動化するプロジェクトは、人工知能への私の関心の高まりから始まりました。OpenAI GPT-4 と Mistral AI の API に関する初期の経験に触発され、これらの技術を実践的なプロジェクトに具体化し、ブログに具体的な価値を提供するという考えに魅了されました。これは単に AI ツールの習得を目指すだけでなく、デジタル空間を豊かにするために自動化と革新を融合させたいという願望でもありました。

このプロジェクトは、AI が単なる執筆テーマではなく、開発における積極的なパートナーとなる冒険に変わりました。AI を使用して記事を簡単かつ効率的に翻訳し、その自動化機能を探索するという考えは、魅力的な展望を開きました。これは、言語の壁を超えてコンテンツをより広い読者に届ける機会であり、絶えず進化する人工知能の分野を航行する機会でもありました。

課題

主な課題は、記事の元の形式を保持しながら正確に翻訳するスクリプトを作成することでした。特にコードブロック、リンク、画像などを含めて翻訳することが必要でした。もう一つの課題は、スクリプトがさまざまな言語に簡単に対応できるようにすることでした。 また、この構造を考慮できる必要がありました。

├── content
│   ├── about
│   │   └── a-propos-du-blog-jls42.md
│   ├── mentions
│   │   └── mentions-legales.md
│   ├── posts
│   │   ├── blog
│   │   │   └── nouveau-theme-logo.md
│   │   ├── ia
│   │   │   ├── poc-mistral-ai-mixtral.md
│   │   │   ├── poc-openai-api-gpt4.md
│   │   │   └── stable-difusion-aws-ec2.md
│   │   ├── infrastructure
│   │   │   └── infrastruture-as-code-serverless-ha-jls42-org.md
│   │   └── raspberry-pi
│   │       ├── glusterfs_distribue_replique_sur_raspberry_pi_via_ansible.md
│   │       ├── initialisation-auto-de-raspbian-sur-raspberry-pi.md
│   │       ├── installation-de-docker-sur-raspberry-pi-via-ansible.md
│   │       └── installation-de-kubernetes-sur-raspberry-pi-via-ansible.md

ソリューション: 革新的なスクリプト

私は、非テキスト要素を保持しながらテキストを翻訳するために OpenAI GPT-4 API を利用する Python スクリプトを設計しました。一連の処理ルールとプレースホルダーの使用により、スクリプトはコードブロックやその他の翻訳不可能な要素を識別・除外し、翻訳されたコンテンツが元の内容に忠実であることを保証します。

主な機能

  1. GPT-4による正確な翻訳 : スクリプトは OpenAI の GPT-4 モデルを使用してフランス語のテキストを英語に翻訳し、元のコンテンツの品質とニュアンスを保持します。
  2. フォーマットの維持 : 翻訳中にコードブロック、URL、および画像のパスが識別され、そのまま残されるため、元のフォーマットが維持されます。
  3. 多言語対応の柔軟性 : スクリプトは、異なるソース言語とターゲット言語に簡単に適応できるように設計されており、多様な多言語アプリケーションを可能にします。
  4. Markdownファイルのサポート : Markdown 形式で記述されたドキュメントを翻訳し、その特定の構造とフォーマットを保持する能力があります。
  5. ディレクトリの翻訳自動化 : 指定されたディレクトリおよびそのサブディレクトリに見つかる Markdown ファイルを自動的に翻訳し、大量のコンテンツ管理を容易にします。
  6. 翻訳ノートの統合 : 翻訳されたドキュメントの最後に、使用された GPT モデルを示す翻訳ノートを自動的に追加します。
  7. 簡単な設定とカスタマイズ : API キー、GPT モデル、ソース言語とターゲット言語、ファイルのディレクトリのデフォルト設定がカスタマイズ可能で、使いやすさの柔軟性を提供します。 パフォーマンスレポート : スクリプトは各ファイルの翻訳に必要な時間のフィードバックを提供し、パフォーマンスを監視できるようにします。

スクリプトのコード

コードはこちらにもあります: AI-Powered Markdown Translator

#!/usr/bin/env python3

import os
import argparse
import time
from openai import OpenAI
import re

# Initialisation de la configuration avec les valeurs par défaut
DEFAULT_API_KEY = 'votre-clé-api-par-défaut'
DEFAULT_MODEL = "gpt-4-1106-preview"
DEFAULT_SOURCE_LANG = 'fr'
DEFAULT_TARGET_LANG = 'en'
DEFAULT_SOURCE_DIR = 'content/posts'
DEFAULT_TARGET_DIR = 'traductions_en'

MODEL_TOKEN_LIMITS = {
    "gpt-4-1106-preview": 4096,
    "gpt-4-vision-preview": 4096,
    "gpt-4": 8192,
    "gpt-4-32k": 32768,
    "gpt-4-0613": 8192,
    "gpt-4-32k-0613": 32768
}

# Fonction de traduction
def translate_with_openai(text, client, args):
    """
    Traduit le texte donné du langage source au langage cible en utilisant l'API OpenAI.
    
    Args:
        text (str) : Le texte à traduire.
        client : L'objet client OpenAI.
        args : Les arguments contenant les informations sur le langage source, le langage cible et le modèle.
        
    Returns:
        str : Le texte traduit.
    """
    # Détecter et stocker les blocs de code
    code_blocks = re.findall(r'(^```[a-zA-Z]*\n.*?\n^```)', text, flags=re.MULTILINE | re.DOTALL)
    placeholders = [f"#CODEBLOCK{index}#" for index, _ in enumerate(code_blocks)]
    
    # Remplacer les blocs de code par des placeholders
    for placeholder, code_block in zip(placeholders, code_blocks):
        text = text.replace(code_block, placeholder)
    
    # Création du message pour l'API
    messages = [
        {"role": "system", "content": f"Translate the following text from {args.source_lang} to {args.target_lang}, ensuring that elements such as URLs, image paths, and code blocks (delimited by ```) are not translated. Leave these elements unchanged."},
        {"role": "user", "content": text}
    ]
    
    # Envoi de la demande de traduction
    response = client.chat.completions.create(
        model=args.model,
        messages=messages
    )
    
    # Obtenir le texte traduit et remplacer les placeholders par les blocs de code originaux
    translated_text = response.choices[0].message.content.strip()
    for placeholder, code_block in zip(placeholders, code_blocks):
        translated_text = translated_text.replace(placeholder, code_block)

    return translated_text

def add_translation_note(client, args):
    """
    Ajoute une note de traduction à un document.

    Args:
        client : Le client de traduction.
        args : Arguments supplémentaires.

    Returns:
        La note de traduction formatée.
    """
    # Note de traduction en français
    translation_note_fr = "Ce document a été traduit de la version française du blog par le modèle "
    # Traduire la note en langue cible
    translated_note = translate_with_openai(translation_note_fr + args.model, client, args)
    # Formatage de la note de traduction
    return f"\n\n**{translated_note}**\n\n"

# Traitement des fichiers Markdown
def translate_markdown_file(file_path, output_path, client, args):
    """
    Traduit le contenu d'un fichier markdown en utilisant l'API de traduction OpenAI et écrit le contenu traduit dans un nouveau fichier.

    Args:
        file_path (str): Chemin vers le fichier markdown d'entrée.
        output_path (str): Chemin vers le fichier de sortie où le contenu traduit sera écrit.
        client: Client de traduction OpenAI.
        args: Arguments supplémentaires pour le processus de traduction.

    Returns:
        None
    """
    print(f"Traitement du fichier : {file_path}")
    start_time = time.time()

    with open(file_path, 'r', encoding='utf-8') as f:
        content = f.read()

    translated_content = translate_with_openai(content, client, args)
    
    # Ajouter la note de traduction à la fin du contenu traduit
    translation_note = add_translation_note(client, args)
    translated_content_with_note = translated_content + translation_note

    with open(output_path, 'w', encoding='utf-8') as f:
        f.write(translated_content_with_note)

    end_time = time.time()
    print(f"Traduction terminée en {end_time - start_time:.2f} secondes.")

def translate_directory(input_dir, output_dir, client, args):
    """
    Traduit tous les fichiers markdown dans le répertoire d'entrée et ses sous-répertoires.

    Args:
        input_dir (str): Chemin vers le répertoire d'entrée.
        output_dir (str): Chemin vers le répertoire de sortie.
        client: Objet client de traduction.
        args: Arguments supplémentaires pour la traduction.

    Returns:
        None
    """
    for root, dirs, files in os.walk(input_dir, topdown=True):
        # Exclure les dossiers qui commencent par "traductions_"
        dirs[:] = [d for d in dirs if not d.startswith("traductions_")]

        for file in files:
            if file.endswith('.md'):
                file_path = os.path.join(root, file)
                base, _ = os.path.splitext(file)
                # Ajouter le nom du modèle utilisé dans le nom du fichier de sortie
                output_file = f"{base}-{args.model}-{args.target_lang}.md"
                relative_path = os.path.relpath(root, input_dir)
                output_path = os.path.join(output_dir, relative_path, output_file)

                os.makedirs(os.path.dirname(output_path), exist_ok=True)

                if not os.path.exists(output_path):
                    translate_markdown_file(file_path, output_path, client, args)
                    print(f"Fichier '{file}' traité.")


def main():
    """
    Fonction principale pour traduire les fichiers Markdown.

    Args:
        --source_dir (str): Répertoire source contenant les fichiers Markdown.
        --target_dir (str): Répertoire cible pour sauvegarder les traductions.
        --model (str): Modèle GPT à utiliser.
        --target_lang (str): Langue cible pour la traduction.
        --source_lang (str): Langue source pour la traduction.
    """
    parser = argparse.ArgumentParser(description="Traduit les fichiers Markdown.")
    parser.add_argument('--source_dir', type=str, default=DEFAULT_SOURCE_DIR, help='Répertoire source contenant les fichiers Markdown')
    parser.add_argument('--target_dir', type=str, default=DEFAULT_TARGET_DIR, help='Répertoire cible pour sauvegarder les traductions')
    parser.add_argument('--model', type=str, default=DEFAULT_MODEL, help='Modèle GPT à utiliser')
    parser.add_argument('--target_lang', type=str, default=DEFAULT_TARGET_LANG, help='Langue cible pour la traduction')
    parser.add_argument('--source_lang', type=str, default=DEFAULT_SOURCE_LANG, help='Langue source pour la traduction')

    args = parser.parse_args()

    openai_api_key = os.getenv('OPENAI_API_KEY', DEFAULT_API_KEY)
    with OpenAI(api_key=openai_api_key) as client:
        translate_directory(args.source_dir, args.target_dir, client, args)

if __name__ == "__main__":
    main()

スクリプトの概要

モジュールのインポート

まず、必要なモジュールをいくつかインポートします。例えば、osargparsetimereです。これらのモジュールは、ファイルシステム操作、コマンドライン引数の解析、実行時間の測定、テキストの検索と置換操作を行うために使用されます。

定数

次に、DEFAULT_API_KEYDEFAULT_MODELDEFAULT_SOURCE_LANGDEFAULT_TARGET_LANGDEFAULT_SOURCE_DIRDEFAULT_TARGET_DIRなどの定数が定義されています。これらの定数はスクリプトで使用されるデフォルト値を表していますが、コマンドライン引数を指定することで変更できます。

translate_with_openai 関数

次に、translate_with_openai 関数が登場します。この関数はテキスト、OpenAIクライアントオブジェクト、引数をパラメータとして受け取ります。OpenAI APIを使用して、ソース言語からターゲット言語にテキストを翻訳します。以下はその動作の仕組みです:

  1. 関数は正規表現を使用して、テキスト内のコードブロックを検出して保存します。これらのコードブロックはトリプルバッククォート()で区切られています。コードブロックは code_blocks` というリストに保存されます。
  2. 次に、関数はコードブロックをプレースホルダーに置き換えます。プレースホルダーは #CODEBLOCK{index}# という形式の文字列で、index はリスト code_blocks 内の対応するコードブロックのインデックスです。
  3. 関数はOpenAI APIへのメッセージを作成します。このメッセージは2つの部分で構成されます:ソース言語からターゲット言語へのテキスト翻訳をAPIに指示するシステムメッセージと、翻訳するテキストを含むユーザーメッセージです。
  4. 関数は client.chat.completions.create() メソッドを使用して、翻訳リクエストをAPIに送信します。使用するモデルと翻訳するメッセージを指定します。
  5. APIの応答は翻訳されたテキストを含みます。関数は翻訳されたテキストを取得し、プレースホルダーを元のコードブロックに置き換えます。
  6. 最後に、関数は翻訳されたテキストを返します。

add_translation_note 関数

次に、add_translation_note 関数が登場します。この関数はドキュメントに翻訳メモを追加します。この関数はOpenAIクライアントオブジェクトと引数をパラメータとして受け取ります。以下はその動作の仕組みです:

  1. 関数は translation_note_fr 変数を使用して、フランス語で翻訳メモを作成します。
  2. 次に、関数は translate_with_openai 関数を使用して、OpenAI APIを使用して翻訳メモを翻訳します。translate_with_openai に渡される引数には翻訳メモ(フランス語)およびその他の引数が含まれます。
  3. 関数は翻訳された翻訳メモにフォーマットを追加してフォーマットします。
  4. 最後に、関数はフォーマットされた翻訳メモを返します。

translate_markdown_file 関数

次に、translate_markdown_file 関数があります。この関数は入力Markdownファイルのパス、出力ファイルのパス、OpenAIクライアントオブジェクト、および引数をパラメータとして受け取ります。この関数はOpenAI翻訳APIを使用してMarkdownファイルの内容を翻訳し、翻訳された内容を出力ファイルに書き込みます。

このスクリプトは私のブログ記事のアクセシビリティを向上させただけでなく、多言語コンテンツ作成の自動化の新しい可能性を開きました。これは知識の共有におけるより広範で包括的な一歩です。 ## 使用体験と処理時間

使用例

# Création des répertoires cibles
jls42@Boo:~/blog/jls42$ mkdir content/traductions_en content/traductions_es

###############################################
# Demande de traduction à l'IA vers l'anglais #
###############################################
jls42@Boo:~/blog/jls42$ python3 translate.py --source_dir content/ --target_dir content/traductions_en
Traitement du fichier : content/posts/ia/stable-difusion-aws-ec2.md
Traduction terminée en 21.57 secondes.
Fichier 'stable-difusion-aws-ec2.md' traité.
Traitement du fichier : content/posts/ia/poc-openai-api-gpt4.md
Traduction terminée en 34.87 secondes.
Fichier 'poc-openai-api-gpt4.md' traité.
Traitement du fichier : content/posts/ia/poc-mistral-ai-mixtral.md
Traduction terminée en 62.47 secondes.
Fichier 'poc-mistral-ai-mixtral.md' traité.
Traitement du fichier : content/posts/raspberry-pi/installation-de-kubernetes-sur-raspberry-pi-via-ansible.md
Traduction terminée en 46.37 secondes.
Fichier 'installation-de-kubernetes-sur-raspberry-pi-via-ansible.md' traité.
Traitement du fichier : content/posts/raspberry-pi/installation-de-docker-sur-raspberry-pi-via-ansible.md
Traduction terminée en 10.08 secondes.
Fichier 'installation-de-docker-sur-raspberry-pi-via-ansible.md' traité.
Traitement du fichier : content/posts/raspberry-pi/initialisation-auto-de-raspbian-sur-raspberry-pi.md
Traduction terminée en 17.17 secondes.
Fichier 'initialisation-auto-de-raspbian-sur-raspberry-pi.md' traité.
Traitement du fichier : content/posts/blog/nouveau-theme-logo.md
Traduction terminée en 12.91 secondes.
Fichier 'nouveau-theme-logo.md' traité.
Traitement du fichier : content/posts/infrastructure/infrastruture-as-code-serverless-ha-jls42-org.md
Traduction terminée en 12.64 secondes.
Fichier 'infrastruture-as-code-serverless-ha-jls42-org.md' traité.
Traitement du fichier : content/mentions/mentions-legales.md
Traduction terminée en 11.90 secondes.
Fichier 'mentions-legales.md' traité.
Traitement du fichier : content/about/a-propos-du-blog-jls42.md
Traduction terminée en 18.72 secondes.
Fichier 'a-propos-du-blog-jls42.md' traité.

################################################
# Demande de traduction à l'IA vers l'espagnol #
################################################
jls42@Boo:~/blog/jls42$ python3 translate.py --source_dir content/ --target_dir content/traductions_es --target_lang es
Traitement du fichier : content/posts/ia/stable-difusion-aws-ec2.md
Traduction terminée en 33.19 secondes.
Fichier 'stable-difusion-aws-ec2.md' traité.
Traitement du fichier : content/posts/ia/poc-openai-api-gpt4.md
Traduction terminée en 25.24 secondes.
Fichier 'poc-openai-api-gpt4.md' traité.
Traitement du fichier : content/posts/ia/poc-mistral-ai-mixtral.md
Traduction terminée en 58.78 secondes.
Fichier 'poc-mistral-ai-mixtral.md' traité.
Traitement du fichier : content/posts/raspberry-pi/installation-de-kubernetes-sur-raspberry-pi-via-ansible.md
Traduction terminée en 17.64 secondes.
Fichier 'installation-de-kubernetes-sur-raspberry-pi-via-ansible.md' traité.
Traitement du fichier : content/posts/raspberry-pi/installation-de-docker-sur-raspberry-pi-via-ansible.md
Traduction terminée en 19.60 secondes.
Fichier 'installation-de-docker-sur-raspberry-pi-via-ansible.md' traité.
Traitement du fichier : content/posts/raspberry-pi/initialisation-auto-de-raspbian-sur-raspberry-pi.md
Traduction terminée en 37.12 secondes.
Fichier 'initialisation-auto-de-raspbian-sur-raspberry-pi.md' traité.
Traitement du fichier : content/posts/blog/nouveau-theme-logo.md
Traduction terminée en 18.91 secondes.
Fichier 'nouveau-theme-logo.md' traité.
Traitement du fichier : content/posts/infrastructure/infrastruture-as-code-serverless-ha-jls42-org.md
Traduction terminée en 30.73 secondes.
Fichier 'infrastruture-as-code-serverless-ha-jls42-org.md' traité.
Traitement du fichier : content/mentions/mentions-legales.md
Traduction terminée en 13.14 secondes.
Fichier 'mentions-legales.md' traité.
Traitement du fichier : content/about/a-propos-du-blog-jls42.md
Traduction terminée en 11.24 secondes.
Fichier 'a-propos-du-blog-jls42.md' traité.

処理時間

  • 英語 : 約4分 (248.70秒)
  • スペイン語 : 約4.7分 (284.05秒)
  • 合計 : 約8.7分 (532.75秒) これらの時間は、スクリプトの効率と速度を示しています。

結果

これらの翻訳コンテンツの生成結果に、次のリンクからアクセスできます:

このブログポストは、AIによる翻訳自動化の私の経験を凝縮したものです。プログラミングとAIを組み合わせると、可能性はほぼ無限であり、知識の共有とコンテンツのアクセス可能性の分野において新しい、刺激的な視点を開くことが証明されています。

このドキュメントは、fr バージョンから ja 言語に gpt-4o モデルを使用して翻訳されました。翻訳プロセスの詳細については、https://gitlab.com/jls42/ai-powered-markdown-translator をご覧ください。