前回に引き続き、AIエージェントを作るための処理をつくっていきます。
今回は、Copilotが判断するためにテキスト抽出する処理を書きます。
前回まではこちらです。
注意事項
・Copilot Studioを使うのが初めて、Power Automateもあまり使っていないため、MS CopilotとGithub Copilotを使って試行錯誤しながら進めています。そのため、最適な方法ではない可能性があります。
1. 全体の処理の流れ
前回の記事の再掲ですが、このように考えています。
[1] ユーザからPowerPointファイルのアップロードを受け付ける [Copilot Studio]
[2] 受け取ったPowerPointファイルをPower Automateに渡す [Copilot Studio -> Power Automate]
[3] Power Automateの中で、Azure Functionに処理を渡す [Power Automate -> Azure Function (関数アプリ)]
[4] Azure Functionの中でPowerPointファイルを受け取ってテキストを抽出。抽出した結果をPower Automateに返す [Azure Function -> Power Automate]
[5] Power AutomateからCopilot Studioに結果を渡す [Power Automate -> Copilot Studio]
[6] Copilot Studioで誤字脱字チェックを実行する [Copilot Studio]
これまでで[1], [2]に対応しました。今回は先に[4]に書かれているテキスト抽出処理を説明します。Azure Functionでの動作、[2]->[3], [3]->[4]は後日確認して動いたら別の記事で紹介します。
2. PowerPointファイルを受け取ってテキストを抽出
GitHub Copilotを使いつつ、PythonでPowerPointファイルのテキストを抽出する処理を書きました。
フォルダ構成はこのようになっています。
個別ファイルの内容です。
◾️init.py
import logging import azure.functions as func import io from pptx import Presentation def main(req: func.HttpRequest) -> func.HttpResponse: logging.info('Python HTTP trigger function processed a request.') try: # リクエストからファイルを取得 file = req.files.get('file') if not file: return func.HttpResponse( "ファイルがアップロードされていません。PowerPointファイルをアップロードしてください。", status_code=400 ) # ファイル内容を読み込み file_contents = file.stream.read() # PowerPointファイルとして読み込む presentation = Presentation(io.BytesIO(file_contents)) # テキストを抽出 extracted_text = [] for slide in presentation.slides: for shape in slide.shapes: if hasattr(shape, "text"): extracted_text.append(shape.text) # 結果を返す return func.HttpResponse( f"抽出されたテキスト:\n\n{''.join(extracted_text)}", mimetype="text/plain" ) except Exception as e: logging.error(f"エラーが発生しました: {str(e)}") return func.HttpResponse( f"エラーが発生しました: {str(e)}", status_code=500 )
◾️function.json
{ "scriptFile": "__init__.py", "bindings": [ { "authLevel": "function", "type": "httpTrigger", "direction": "in", "name": "req", "methods": ["post"], "route": null }, { "type": "http", "direction": "out", "name": "$return" } ] }
◾️host.json
{ "version": "2.0", "logging": { "applicationInsights": { "samplingSettings": { "isEnabled": true, "excludedTypes": "Request" } } }, "extensionBundle": { "id": "Microsoft.Azure.Functions.ExtensionBundle", "version": "[3.*, 4.0.0)" } }
◾️local.setting.json
{ "IsEncrypted": false, "Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", "FUNCTIONS_WORKER_RUNTIME": "python" }, "Host": { "LocalHttpPort": 7071, "CORS": "*", "CORSCredentials": false } }
◾️requirements.txt
python-pptx azure-functions
◾️index.html (ローカル環境でのテスト用。配置場所はどこでもよい)
<!DOCTYPE html> <html> <head> <title>PowerPoint テキスト抽出</title> <meta charset="UTF-8"> <style> body { font-family: sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; } .error { color: red; } .success { color: green; } pre { background: #f5f5f5; padding: 10px; border-radius: 5px; overflow: auto; } </style> </head> <body> <h1>PowerPointファイルからテキストを抽出</h1> <!-- フォーム部分 --> <form id="uploadForm" enctype="multipart/form-data"> <input type="file" name="file" id="fileInput" accept=".pptx"> <button type="button" onclick="uploadFile()">アップロード</button> </form> <!-- 状態表示 --> <div id="status" style="margin-top: 10px;"></div> <!-- 結果表示エリア --> <pre id="result" style="margin-top: 20px;"></pre> <script> const API_URL = 'http://localhost:7071/api/source'; const statusDiv = document.getElementById('status'); const resultDiv = document.getElementById('result'); // デバッグ情報の表示 console.log(`APIエンドポイント: ${API_URL}`); function uploadFile() { const fileInput = document.getElementById('fileInput'); if (!fileInput.files[0]) { statusDiv.textContent = 'ファイルを選択してください'; statusDiv.className = 'error'; return; } const formData = new FormData(); formData.append('file', fileInput.files[0]); statusDiv.textContent = '処理中...'; statusDiv.className = ''; resultDiv.textContent = ''; // デバッグ情報 console.log('リクエスト送信開始...'); fetch(API_URL, { method: 'POST', body: formData }) .then(response => { console.log('レスポンス受信:', response.status); if (!response.ok) { return response.text().then(text => { throw new Error(`HTTP error! status: ${response.status}, message: ${text}`); }); } const contentType = response.headers.get('content-type'); if (contentType && contentType.includes('application/json')) { return response.json(); } else { return response.text().then(text => { return { rawResponse: text }; }); } }) .then(data => { statusDiv.textContent = '処理完了!'; statusDiv.className = 'success'; resultDiv.textContent = JSON.stringify(data, null, 2); console.log('処理結果:', data); }) .catch(error => { statusDiv.textContent = `エラーが発生しました`; statusDiv.className = 'error'; resultDiv.textContent = error.message; console.error('エラー詳細:', error); }); } </script> </body> </html>
これらのファイルを準備したら、Pythonの仮想環境を作ってAPIの呼び出しを受け付けるようにします。以下は私の環境での例です。
# プロジェクトフォルダに移動
cd /Users/limes/Documents/AzureFunction/ExtractText
# Python 仮想環境を作成
python -m venv .venv
# 仮想環境をアクティベート
source .venv/bin/activate
# requirements.txt からライブラリをインストール
pip install -r requirements.txt
# ローカル実行
func start
Found Python version 3.13.3 (python3).
Azure Functions Core Tools
Core Tools Version: 4.1.0+7ff2567e43c1ae7471cea7394452c1447661e175 (64-bit)
Function Runtime Version: 4.1040.300.25317
[2025-07-25T22:18:54.759Z] Attempt to remove module cache for google._upb but failed with 'google'. Using the original module cache.
[2025-07-25T22:18:54.759Z] Attempt to remove module cache for google._upb but failed with 'google'. Using the original module cache.
[2025-07-25T22:18:54.759Z] Attempt to remove module cache for google._upb but failed with 'google'. Using the original module cache.
[2025-07-25T22:18:54.759Z] Attempt to remove module cache for google._upb but failed with 'google'. Using the original module cache.
[2025-07-25T22:18:55.262Z] Worker process started and initialized.
Functions:
source: [POST] http://localhost:7071/api/
ローカル環境でindex.htmlをダブルクリックするとブラウザで表示されます。
ここで任意のPowerPointファイルをアップロードすると、抽出されたテキストが表示されます。
今回使ったPowerPointファイルです。以前Babylon.jsレシピ集の執筆者募集に使ったファイルから2ページまで減らしてみました。
これをテキスト抽出させた結果です。
赤枠部分が抽出されたテキストです。これだと見えないので書き出しました。
{ "rawResponse": "抽出されたテキスト:\n\n執筆したことないけど?毎回初めての方が参加されます。\nGitHubで原稿を管理し、オンラインで完結します。\nテキストエディタとブラウザさえあれば執筆・製本確認できるので、気軽に参加できます。どうやって執筆するの?Re:VIEWという書籍向けのマークアップ言語で記述します。\n使用するGitHubリポジトリには、Re:VIEW記法の例を書いているので、それを参考にしてください。" }
ページの区分けは読み取れませんが、一通りのテキストは読めました。ただし、スライドテンプレートに埋め込んだBabylon.js User Community in Japanなどは対象外でした。
3. おわりに
相変わらず進みが遅いですが、PythonでPowerPointのテキストを抽出するところまではできました。次はこれをAzure Functionで動かすところを試してみます。


