LangChain + Gemini 2.5 Flash 日本語-英語翻訳ツール(画像アップロード可能)(ソースコードと実行結果)

Python開発環境,ライブラリ類

ここでは、最低限の事前準備について説明する。機械学習や深層学習を行う場合は、NVIDIA CUDA、Visual Studio、Cursorなどを追加でインストールすると便利である。これらについては別ページ https://www.kkaneko.jp/cc/dev/aiassist.htmlで詳しく解説しているので、必要に応じて参照してください。

Python 3.12 のインストール

インストール済みの場合は実行不要。

管理者権限でコマンドプロンプトを起動(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行する。管理者権限は、wingetの--scope machineオプションでシステム全体にソフトウェアをインストールするために必要である。

REM Python をシステム領域にインストール
winget install --scope machine --id Python.Python.3.12 -e --silent
REM Python のパス設定
set "PYTHON_PATH=C:\Program Files\Python312"
set "PYTHON_SCRIPTS_PATH=C:\Program Files\Python312\Scripts"
echo "%PATH%" | find /i "%PYTHON_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%PYTHON_PATH%" /M >nul
echo "%PATH%" | find /i "%PYTHON_SCRIPTS_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%PYTHON_SCRIPTS_PATH%" /M >nul

関連する外部ページ

Python の公式ページ: https://www.python.org/

AI エディタ Windsurf のインストール

Pythonプログラムの編集・実行には、AI エディタの利用を推奨する。ここでは,Windsurfのインストールを説明する。

管理者権限でコマンドプロンプトを起動(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行して、Windsurfをシステム全体にインストールする。管理者権限は、wingetの--scope machineオプションでシステム全体にソフトウェアをインストールするために必要となる。

winget install --scope machine Codeium.Windsurf -e --silent

関連する外部ページ

Windsurf の公式ページ: https://windsurf.com/

必要なライブラリのインストール

コマンドプロンプトを管理者として実行(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行する


pip install requests

LangChain + Gemini 2.5 Flash 日本語-英語翻訳ツール(画像アップロード可能)(ソースコードと実行結果)

ソースコード


# LangChain + Gemini 2.5 Flash 日本語-英語翻訳ツール(画像アップロード可能)(ソースコードと実行結果)
# 特徴技術名: Gemini 2.5 API
# 出典: Gemini Team, Google. (2025). Gemini 2.5: Pushing the Frontier with Advanced Reasoning, Multimodality, Long Context, and Next Generation Agentic Capabilities. Google DeepMind. https://storage.googleapis.com/deepmind-media/gemini/gemini_v2_5_report.pdf
# 特徴機能: 推論機能による文脈理解翻訳(思考プロセスを経た推論により、単純な文字変換ではなく文脈や文化的ニュアンスを適切に理解して翻訳)
# 方式設計:
#   関連利用技術: Python requests(HTTP API通信)、JSON(データ交換)、Base64エンコーディング(画像送信)、pathlib(ファイル操作)
#   入力と出力: 入力: 日本語テキスト(直接入力/ファイル読み込み/サンプル選択)、出力: 英語翻訳結果をコンソールとresult.txtファイルに出力
#   処理手順: APIキー取得→スタイル設定→Gemini API呼び出し→結果整形・出力
#   前処理、後処理: 前処理: 文脈情報付与、スタイル指定、後処理: 結果整形、エラーハンドリング
#   追加処理: マルチモーダル翻訳、一括翻訳処理
#   調整を必要とする設定値: GEMINI_API_KEY(APIキー)、temperature(一貫性制御、推奨0.1)、style(翻訳スタイル)
# 将来方策: APIキー取得の自動化機能(専用ヘルパーツールによる取得支援・環境変数設定自動化)
# その他の重要事項: Windows環境対応、Python 3.10以上、Google AI Studio APIキー必要
# 前準備: pip install requests

import os
import re
import time
import tkinter as tk
import urllib.request
from datetime import datetime
from pathlib import Path
from tkinter import filedialog
import requests

# 定数定義
TEMPERATURE = 0.1
TOP_K = 40
TOP_P = 0.95
MAX_OUTPUT_TOKENS = 8192
BASE_URL = "https://generativelanguage.googleapis.com/v1beta"
MODEL_NAME = "gemini-2.5-flash"
API_KEY_VARS = ['GEMINI_API_KEY', 'GOOGLE_API_KEY']
ENV_FILES = ['.env', '.env.development', '.env.production']

results_log = []

def load_api_key():
    # 環境変数から取得
    for var_name in API_KEY_VARS:
        if api_key := os.getenv(var_name):
            return api_key

    # .envファイルから取得
    for env_file in ENV_FILES:
        if Path(env_file).exists():
            try:
                content = Path(env_file).read_text()
                for var_name in API_KEY_VARS:
                    if match := re.search(rf'^\s*(?:export\s+)?{var_name}\s*=(.+)$', content, re.M):
                        return match.group(1).strip().strip('"\'')
            except Exception:
                continue
    return None

def show_api_key_setup_instructions():
    # APIキー取得手順を表示
    print("=" * 60)
    print("Gemini APIキーが見つかりません")
    print("=" * 60)
    print()
    print("このプログラムを使用するには、Gemini APIキーが必要です。")
    print()
    print("APIキー取得手順:")
    print("1. または手動でAPIキーを取得:")
    print("   - https://aistudio.google.com/app/apikey にアクセス")
    print("   - Googleアカウントでログイン")
    print("   - 'Get API key' → 'Create API key' をクリック")
    print("   - 'Create API key in new project' を選択")
    print("   - 生成されたAPIキーをコピー")
    print()
    print("参考: 「Gemini APIキー取得支援ツール」を用意しています")
    print("   https://www.kkaneko.jp/ai/labo/geminiapikey.html")
    print()
    print("2. 設定")
    print("   **1. 環境変数での設定**")
    print("   - `GEMINI_API_KEY` または `GOOGLE_API_KEY` を環境変数に設定")
    print("   **2. .envファイルでの設定**")
    print("   - プログラムと同じフォルダに `.env` ファイルを作成し、以下のように記述:")
    print("   GEMINI_API_KEY=your_api_key_here")
    print("   または")
    print("   GOOGLE_API_KEY=your_api_key_here")
    print()
    print("3. 設定後、このプログラムを再実行してください")
    print("=" * 60)

def get_error_message(status_code, response_text=""):
    # HTTPステータスコードに応じたエラーメッセージを生成
    error_messages = {
        400: "リクエストが正しくありません。入力データを確認してください。",
        401: "APIキーが無効です。正しいAPIキーを設定してください。",
        403: "アクセスが拒否されました。APIキーの権限を確認してください。",
        404: "指定されたモデルが見つかりません。モデル名を確認してください。",
        429: "APIの利用制限に達しました。しばらく待ってから再試行してください。",
        500: "サーバー内部エラーが発生しました。しばらく待ってから再試行してください。",
        502: "ゲートウェイエラーが発生しました。しばらく待ってから再試行してください。",
        503: "サービスが一時的に利用できません。メンテナンス中の可能性があります。しばらく待ってから再試行してください。",
        504: "タイムアウトエラーが発生しました。ネットワーク接続を確認してください。"
    }

    base_message = error_messages.get(status_code, f"不明なエラーが発生しました (ステータスコード: {status_code})")

    # レスポンステキストから追加情報を抽出
    if response_text:
        try:
            import json
            error_data = json.loads(response_text)
            if 'error' in error_data and 'message' in error_data['error']:
                base_message += f"\n詳細: {error_data['error']['message']}"
        except:
            pass

    return base_message

def handle_api_errors(func):
    """API呼び出しエラーハンドリングデコレータ"""
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except requests.exceptions.ConnectionError:
            return {"error": "インターネット接続エラー: ネットワーク接続を確認してください。"}
        except requests.exceptions.Timeout:
            return {"error": "タイムアウトエラー: サーバーからの応答がありません。"}
        except Exception as e:
            return {"error": f"処理エラー: {str(e)}"}
    return wrapper

def select_file(title, file_types):
    """ファイル選択の共通処理"""
    root = tk.Tk()
    root.withdraw()
    return filedialog.askopenfilename(title=title, filetypes=file_types)

def select_from_samples(sample_list, prompt_message):
    """サンプル選択の共通処理"""
    print(f"\n{prompt_message}:")
    for i, item in enumerate(sample_list, 1):
        display_text = item if isinstance(item, str) else item.split('/')[-1]
        print(f"{i}: {display_text}")

    try:
        choice = int(input(f"選択 (1-{len(sample_list)}): ")) - 1
        if 0 <= choice < len(sample_list):
            return sample_list[choice]
        else:
            return sample_list[0]
    except ValueError:
        return sample_list[0]

def get_multiline_input(prompt_message):
    """複数行入力機能(空行で入力終了)"""
    print(f"\n{prompt_message}")
    print("(複数行入力可、空の行でEnterで入力終了):")
    input_lines = []
    while True:
        line = input()
        if line == "":
            break
        input_lines.append(line)
    return "\n".join(input_lines).strip()

def generate_result_filename():
    """結果ファイル名生成"""
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    return f"gemini_translation_result_{timestamp}.txt"

def display_translation_settings(jp_text, style, context, model_name):
    """翻訳設定確認表示"""
    print("\n=== 翻訳設定確認 ===")
    print(f"使用モデル: {model_name}")
    print(f"翻訳スタイル: {style}")
    print(f"原文: {jp_text[:100]}{'...' if len(jp_text) > 100 else ''}")
    if context:
        print(f"文脈情報: {context}")
    print("=" * 50)

def handle_result_display(result, translation_type, processing_time):
    """結果表示とエラーハンドリングの統合処理"""
    timestamp = datetime.fromtimestamp(processing_time).strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]

    if "error" not in result:
        print(f"{timestamp} {translation_type}完了")
        print(f"原文: {result['original']}")
        print(f"翻訳: {result['translation']}")
        print(f"モデル: {result['model']}")

        # ログに追加
        results_log.extend([
            f"{timestamp} {translation_type}",
            f"原文: {result['original']}",
            f"翻訳: {result['translation']}",
            f"モデル: {result['model']}",
            "---"
        ])
    else:
        print(f"\n{timestamp} エラーが発生しました")
        print(f"エラー: {result['error']}")

        # 対処法の提案
        if 'status_code' in result:
            print("\n対処法:")
            status_code = result['status_code']
            if status_code == 401:
                print("  1. APIキーが正しく設定されているか確認してください")
                print("  2. Google AI Studioで新しいAPIキーを生成してください")
            elif status_code == 429:
                print("  1. 数分待ってから再試行してください")
                print("  2. APIの利用制限を確認してください")
            elif status_code == 404:
                print("  1. モデル名が正しいか確認してください")
                print("  2. 利用可能なモデル: gemini-2.5-flash, gemini-1.5-pro")
            elif status_code in [500, 502, 503]:
                print("  1. 5-10分待ってから再試行してください")
                print("  2. Google AI Studioのステータスページを確認してください")

        results_log.append(f"{timestamp} エラー: {result['error']}")

class GeminiTranslator:
    def __init__(self, api_key):
        self.api_key = api_key

    def _get_headers(self):
        """共通HTTPヘッダーを取得"""
        return {
            "Content-Type": "application/json",
            "x-goog-api-key": self.api_key
        }

    def _create_generation_config(self):
        """共通生成設定を作成"""
        return {
            "temperature": TEMPERATURE,
            "topK": TOP_K,
            "topP": TOP_P,
            "maxOutputTokens": MAX_OUTPUT_TOKENS
        }

    def _send_request(self, request_data):
        """共通API リクエスト送信処理"""
        api_url = f"{BASE_URL}/models/{MODEL_NAME}:generateContent"

        response = requests.post(
            api_url,
            headers=self._get_headers(),
            json=request_data
        )

        if response.status_code == 200:
            result = response.json()
            translation_text = result["candidates"][0]["content"]["parts"][0]["text"]
            return {"translation": translation_text.strip()}
        else:
            error_message = get_error_message(response.status_code, response.text)
            return {
                "error": error_message,
                "status_code": response.status_code
            }

    @handle_api_errors
    def translate_text(self, japanese_text, context_info="", translation_style="normal"):
        # スタイル別プロンプト設定
        style_prompts = {
            "normal": "自然で読みやすい英語に翻訳してください。",
            "formal": "フォーマルなビジネス英語に翻訳してください。",
            "casual": "カジュアルで親しみやすい英語に翻訳してください。",
            "technical": "専門的で正確な技術英語に翻訳してください。",
            "image_prompt": "画像生成AI(Stable Diffusion、DALL-E、Midjourneyなど)が理解しやすい詳細で具体的な英語プロンプトに変換してください。視覚的要素、構図、照明、スタイル、品質設定などを含む包括的な記述にしてください。"
        }

        system_prompt = f"""あなたは日本語から英語への専門翻訳者です。
以下の要件に従って翻訳してください:

1. 文脈と文化的ニュアンスを適切に保持する
2. 原文の意図と感情を正確に伝える
3. {style_prompts.get(translation_style, style_prompts["normal"])}
4. 慣用句や比喩表現は自然な英語表現に変換する

{f'文脈情報: {context_info}' if context_info else ''}"""

        request_data = {
            "contents": [
                {
                    "parts": [
                        {
                            "text": f"{system_prompt}\n\n翻訳対象:\n{japanese_text}"
                        }
                    ]
                }
            ],
            "generationConfig": self._create_generation_config()
        }

        result = self._send_request(request_data)

        if "error" not in result:
            result.update({
                "original": japanese_text,
                "style": translation_style,
                "model": "Gemini 2.5 Flash"
            })

        return result

    def translate_batch(self, text_list, context_info="", translation_style="normal"):
        batch_results = []

        for i, text in enumerate(text_list):
            print(f"翻訳中 ({i+1}/{len(text_list)}): {text[:50]}...")
            result = self.translate_text(text, context_info, translation_style)
            batch_results.append(result)

        return batch_results

    @handle_api_errors
    def translate_with_multimodal(self, japanese_text, image_file_path):
        # 画像をBase64エンコード
        import base64
        try:
            with open(image_file_path, "rb") as image_file:
                encoded_image_data = base64.b64encode(image_file.read()).decode('utf-8')
        except Exception as e:
            return {"error": f"画像読み込みエラー: {str(e)}"}

        request_data = {
            "contents": [
                {
                    "parts": [
                        {
                            "text": f"""画像の内容を参考にして、以下の日本語テキストを
適切な英語に翻訳してください。画像の文脈を考慮して、
最も自然で正確な翻訳を提供してください。

翻訳対象: {japanese_text}"""
                        },
                        {
                            "inline_data": {
                                "mime_type": "image/jpeg",
                                "data": encoded_image_data
                            }
                        }
                    ]
                }
            ],
            "generationConfig": self._create_generation_config()
        }

        result = self._send_request(request_data)

        if "error" not in result:
            result.update({
                "original": japanese_text,
                "method": "multimodal",
                "model": "Gemini 2.5 Flash"
            })

        return result

def main():
    print("=== Gemini 2.5 Flash 日本語-英語翻訳システム ===")
    print("プログラム概要: Gemini 2.5 Flashの推論機能を使用した日本語から英語への翻訳")
    print("操作方法: メニューに従って入力方法を選択し、翻訳スタイルを指定してください")
    print()

    # APIキーの読み込み
    api_key = load_api_key()
    if not api_key:
        show_api_key_setup_instructions()
        return

    translator = GeminiTranslator(api_key)

    try:
        # テキスト入力取得
        print("日本語テキスト翻訳システム")
        print("操作方法:")
        print("0: テキストファイルを選択")
        print("1: 直接入力")
        print("2: サンプルテキスト")
        input_choice = input("選択: ")

        if input_choice == '0':
            selected_file_path = select_file("テキストファイルを選択", [("テキストファイル", "*.txt"), ("全てのファイル", "*.*")])
            if not selected_file_path:
                return
            try:
                japanese_text = Path(selected_file_path).read_text(encoding='utf-8').strip()
            except Exception as e:
                print(f"ファイル読み込みエラー: {e}")
                return
        elif input_choice == '1':
            japanese_text = get_multiline_input("翻訳したい日本語テキストを入力してください")
        else:  # input_choice == '2' or その他
            sample_texts = [
                "お疲れさまです。本日の会議資料をお送りします。",
                "ようやく涼しくなってきました。今度一緒にお茶でもいかがですか?",
                "AIの便利さを実感しましょう。",
                "申し訳ございませんが、明日の会議に出席できなくなりました。",
                "私はうきうきした気持ちだ。楽しさのあまり、叫びだしたい気持ちを必死に抑えた。"
            ]
            japanese_text = select_from_samples(sample_texts, "サンプルテキスト")

        if not japanese_text:
            print("テキストが取得できませんでした。")
            return

        # 画像入力取得(マルチモーダル翻訳用)
        print("\nマルチモーダル翻訳(画像+テキスト)を使用しますか?")
        print("0: 画像ファイルを選択")
        print("1: 使用しない(テキストのみ)")
        print("2: サンプル画像")
        multimodal_choice = input("選択: ")

        image_file_path = None
        if multimodal_choice == '0':
            image_file_path = select_file("画像ファイルを選択", [("画像ファイル", "*.jpg *.jpeg *.png *.gif *.bmp"), ("全てのファイル", "*.*")])
        elif multimodal_choice == '2':
            sample_image_urls = [
                "https://raw.githubusercontent.com/opencv/opencv/master/samples/data/fruits.jpg",
                "https://raw.githubusercontent.com/opencv/opencv/master/samples/data/messi5.jpg",
                "https://raw.githubusercontent.com/opencv/opencv/master/samples/data/aero3.jpg",
                "https://upload.wikimedia.org/wikipedia/commons/3/3a/Cat03.jpg"
            ]

            selected_image_url = select_from_samples(sample_image_urls, "サンプル画像")
            if selected_image_url:
                image_filename = f"sample_{sample_image_urls.index(selected_image_url)}.jpg"
                try:
                    urllib.request.urlretrieve(selected_image_url, image_filename)
                    print(f"サンプル画像をダウンロードしました: {image_filename}")
                    image_file_path = image_filename
                except Exception as e:
                    print(f"画像のダウンロードに失敗しました: {e}")

        # 翻訳処理
        processing_start_time = time.time()
        if image_file_path:
            # マルチモーダル翻訳
            print(f"\nマルチモーダル翻訳実行中...")
            display_translation_settings(japanese_text, "multimodal", "画像付き", "Gemini 2.5 Flash")
            translation_result = translator.translate_with_multimodal(japanese_text, image_file_path)
            handle_result_display(translation_result, "マルチモーダル翻訳", processing_start_time)
        else:
            # テキスト翻訳
            print("翻訳スタイルを選択してください:")
            print("1: normal (自然で読みやすい)")
            print("2: formal (フォーマルなビジネス)")
            print("3: casual (カジュアルで親しみやすい)")
            print("4: technical (専門的で正確)")
            print("5: image_prompt (画像生成用の詳細で具体的なプロンプト)")

            style_choice = input("選択 (1-5): ")
            style_options = {
                "1": "normal",
                "2": "formal",
                "3": "casual",
                "4": "technical",
                "5": "image_prompt"
            }
            selected_style = style_options.get(style_choice, "normal")

            # 文脈情報(オプション)
            context_information = get_multiline_input("文脈情報があれば入力してください(省略可)")

            print(f"\nテキスト翻訳実行中... (スタイル: {selected_style})")
            display_translation_settings(japanese_text, selected_style, context_information, "Gemini 2.5 Flash")
            translation_result = translator.translate_text(japanese_text, context_information, selected_style)
            handle_result_display(translation_result, "テキスト翻訳", processing_start_time)

    except KeyboardInterrupt:
        print("\n\nプログラムが中断されました。")

    except Exception as e:
        error_message = f"予期しないエラー: {str(e)}"
        print(f"\n{error_message}")
        results_log.append(error_message)

    finally:
        print('\n=== プログラム終了 ===')
        # 結果をファイルに保存
        if results_log:
            try:
                result_filename = generate_result_filename()
                Path(result_filename).write_text(
                    f'=== Gemini 2.5 Flash 翻訳結果 ===\n'
                    f'実行日時: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}\n'
                    f'\n'
                    f'{chr(10).join(results_log)}',
                    encoding='utf-8'
                )
                print(f'\n処理結果を{result_filename}に保存しました')
            except Exception as e:
                print(f"ファイル保存エラー: {e}")

if __name__ == "__main__":
    main()