先日、下記のつぶやきを拝見して心が躍りました。
マイクロソフトが開発した MarkItDown
— daka | Microsoft | AI (@daiki15036604) December 14, 2024
PDFだけじゃなく、Word、Excel、PowerPointなどの多様なファイル形式をMarkdownに変換し、テキスト分析やインデックス作成を容易できるのよい!https://t.co/DgCxamEpTs
マイクロソフトが開発した MarkItDown PDFだけじゃなく、Word、Excel、PowerPointなどの多様なファイル形式をMarkdownに変換し、テキスト分析やインデックス作成を容易できる
非常に心が踊るフレーズです。
さっそくPower Appsで試されている方もいらっしゃいます。
この記事ではAzure Functionsを使って、このMarkItDownを試してみたいと思います。
作ったもの
-
Power Apps-SharePoint サイトのドキュメントライブラリを指定するインターフェース -
Azure Functions- MarkItDownを関数用に加工したもの -
Power Automate- (1)のPower Appsから、ファイル識別子を受け取り、ファイル コンテンツを取得、Azure Functionsを起動
Power Appsはあくまで結果のプレビュー用に用いています。
キャプチャを載せると、ファイルに対して結果がある程度上手くかえってきていることがわかります。
■ Word
■ PowerPoint
実際につかったところ、PowerPointやWord、PDFのMarkdownへの変換は精度が高いです。
Power Appsでは、赤枠のギャラリー コントロールにドキュメント ライブラリの結果を出力し、OnSelectプロパティで結果を取得しています。
UpdateContext({Result: MarkdownConvert.Run(ThisItem.識別子).response});
結果はテキスト入力で表示しています。
小ネタ: 環境変数
Power Apps、Power AutomateでSharePointのサイトやリスト情報であったり、今回はAzure Functionsのエンドポイントを指定したりしますが、そのような値は環境変数に設定しておくと管理が容易です。
ソリューションを作成し、環境変数を追加すると、Power AppsとPower Automateの共通の値(例: データソースの情報)、エンドポイントの情報をまとめて管理出来ます。
また開発に置ける環境戦略開発者環境、テスト環境、本稼働環境を分ける際にも非常に重宝する機能です。
今回は下記の値を環境変数として定義します。
| 項目名 | 環境変数名 | 説明 |
|---|---|---|
| SharePoint サイト | SP_SITE_URL |
SharePoint サイト |
| ドキュメント ライブラリ | SP_DOCUMENT_LIBRARY |
SharePoint ドキュメントライブラリ |
| Azure Function エンドポイント | AZURE_FUNCTION_ENDPOINT |
Azure Functions URL |
| シークレット | AZURE_SECRET |
シークレット情報 |
Azure Functions - MarkItDownを関数用に加工したもの
Visual Studio Code 用 Azure Functions 拡張機能を使って作成を進めます。
私のような知識が少ないものでも簡単に作れる利便性の高いツールです。
作業するディレクトリを選択し、Azure Functions プロジェクトを作成します。
HTTP トリガー関数テンプレートを選択することで必要なファイルがそろいます。
Pythonの仮想環境はvenvを使います。
markitdown-function/
├── .venv/
├── markitdown/
│ ├── __init__.py
│ ├── __about__.py
│ └── _markitdown.py
├── function_app.py
├── requirements.txt
├── host.json
└── local.settings.json
テンプレートの中にmarkitdownディレクトリを設けて、MarkItDownの必要なファイルを組み込みます。
必要なファイルは下記の三つです。
- init.py
- about.py
- markitdown.py
依存関係
azure-functions以外はMarkItDownで使われるものです。
azure-functions
beautifulsoup4>=4.9.3
markdownify>=0.11.6
mammoth>=1.5.1
pandas>=1.3.0
pdfminer.six>=20201018
python-pptx>=0.6.21
puremagic>=1.15
requests>=2.26.0
charset-normalizer>=2.0.0
function_app.py
エントリーポイントであるfunction_app.pyはClaude 3.5 Sonnetに作成してもらいました。
AI様様です。
Power Automateからbase64文字列で受け取るファイルをもとにMarkItDownを呼び出します。
import azure.functions as func
import logging
import json
import base64
import tempfile
import os
from markitdown import MarkItDown, FileConversionException, UnsupportedFormatException
app = func.FunctionApp()
@app.route(route="convert", auth_level=func.AuthLevel.FUNCTION)
async def convert_to_markdown(req: func.HttpRequest) -> func.HttpResponse:
logging.info('Python HTTP trigger function processed a request.')
try:
# Get request body
req_body = req.get_json()
if not req_body or 'file' not in req_body:
return func.HttpResponse(
json.dumps({
"error": "Please pass a base64 encoded file in the request body"
}),
mimetype="application/json",
status_code=400
)
file_content = req_body.get('file')
file_extension = req_body.get('extension', '')
# Decode base64 content
try:
decoded_content = base64.b64decode(file_content)
except Exception as e:
return func.HttpResponse(
json.dumps({
"error": f"Invalid base64 content: {str(e)}"
}),
mimetype="application/json",
status_code=400
)
# Create a temporary file
handle, temp_path = tempfile.mkstemp(suffix=file_extension)
try:
with os.fdopen(handle, 'wb') as temp_file:
temp_file.write(decoded_content)
# Initialize MarkItDown converter
converter = MarkItDown()
# Convert the file
result = converter.convert_local(
temp_path,
file_extension=file_extension
)
# Prepare response
response_data = {
"title": result.title,
"content": result.text_content
}
return func.HttpResponse(
body=json.dumps(response_data),
mimetype="application/json",
status_code=200
)
except FileConversionException as e:
return func.HttpResponse(
json.dumps({
"error": f"File conversion error: {str(e)}"
}),
mimetype="application/json",
status_code=422
)
except UnsupportedFormatException as e:
return func.HttpResponse(
json.dumps({
"error": f"Unsupported format: {str(e)}"
}),
mimetype="application/json",
status_code=415
)
except Exception as e:
logging.error(f"Error converting file: {str(e)}")
return func.HttpResponse(
json.dumps({
"error": f"Internal server error: {str(e)}"
}),
mimetype="application/json",
status_code=500
)
finally:
# Clean up temporary file
try:
os.unlink(temp_path)
except:
pass
except Exception as e:
logging.error(f"Error processing request: {str(e)}")
return func.HttpResponse(
json.dumps({
"error": f"Internal server error: {str(e)}"
}),
mimetype="application/json",
status_code=500
)
認証はFunctionレベルとなっています。
こちらをAzure Functionsとしてデプロイすると、Power Automate経由でMarkItDownが使えます。
Power Automate フロー
- Power Apps がフローを呼び出したとき (V2) - SharePoint ドキュメントライブラリのファイル識別子を受け取る
- ファイル コンテンツの取得
-
作成アクション - base64関数でファイル コンテンツの文字列の base64 エンコード バージョンに変換 -
HTTP- Azure Functionsを実行 -
Power Appsに値を返す
前述のとおり、Power Appsからファイル識別子を受け取っています。
こちらからSharePointのファイル コンテンツを取得します。
base64関数で取得したファイルコンテンツを、文字列の base64 エンコード バージョンに変換します。これにより、Azure Functionsに渡す準備が整います。
@{base64(outputs('ファイル_コンテンツの取得')?['body'])}
HTTP - Azure Functionsを実行
URIはデプロイしたリソースから取得します。
今回は環境変数で値を設定しています。
@{parameters('AZURE_FUNCTION_ENDPOINT (gatsuo_AZURE_FUNCTION_ENDPOINT)')}?code=@{parameters('AZURE_SECRET (gatsuo_AZURE_SECRET)')}
Method POST
{
"Content-Type": "application/json"
}
{
"file": "@{outputs('作成_base64')}"
}
設定から入出力をマスキングすることも可能です。
こちらを実行した結果がcontentとして取得できます。
@{body('HTTP')?['content']}
おわりに
構造化された文字列になるとAI活用の幅が広がります。
「Copilot活用のためにAzure Functionsを使いたいです!」は市民開発の立ち位置から打ち手として首を傾げるところですが、こういった技術が普及することにより、ファイルの形式関係なくAIとの対話ができそうなことにワクワクしますね。
今回もブログを読んでいただき、ありがとうございました!












