7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

先日、下記のつぶやきを拝見して心が躍りました。

マイクロソフトが開発した MarkItDown PDFだけじゃなく、Word、Excel、PowerPointなどの多様なファイル形式をMarkdownに変換し、テキスト分析やインデックス作成を容易できる

非常に心が踊るフレーズです。

さっそくPower Appsで試されている方もいらっしゃいます。

この記事ではAzure Functionsを使って、このMarkItDownを試してみたいと思います。

作ったもの

  1. Power Apps - SharePoint サイトのドキュメントライブラリを指定するインターフェース
  2. Azure Functions - MarkItDownを関数用に加工したもの
  3. Power Automate - (1)のPower Appsから、ファイル識別子を受け取り、ファイル コンテンツを取得Azure Functionsを起動

Power Appsはあくまで結果のプレビュー用に用いています。
キャプチャを載せると、ファイルに対して結果がある程度上手くかえってきていることがわかります。

■ Word

image.png

■ PDF

image.png

■ PowerPoint

image.png

実際につかったところ、PowerPointWordPDFのMarkdownへの変換は精度が高いです。

Power Appsでは、赤枠のギャラリー コントロールにドキュメント ライブラリの結果を出力し、OnSelectプロパティで結果を取得しています。

image.png

OnSelect
UpdateContext({Result: MarkdownConvert.Run(ThisItem.識別子).response});

結果はテキスト入力で表示しています。

image.png

小ネタ: 環境変数

Power AppsPower AutomateでSharePointのサイトやリスト情報であったり、今回はAzure Functionsのエンドポイントを指定したりしますが、そのような値は環境変数に設定しておくと管理が容易です。

image.png

Power Platform の概要のための環境変数

ソリューションを作成し、環境変数を追加すると、Power AppsPower 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で使われるものです。

requirements.txt
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.pyClaude 3.5 Sonnetに作成してもらいました。
AI様様です。

Power Automateからbase64文字列で受け取るファイルをもとにMarkItDownを呼び出します。

function_app.py
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 フロー

image.png

  1. Power Apps がフローを呼び出したとき (V2) - SharePoint ドキュメントライブラリのファイル識別子を受け取る
  2. ファイル コンテンツの取得
  3. 作成アクション - base64関数ファイル コンテンツの文字列の base64 エンコード バージョンに変換
  4. HTTP - Azure Functionsを実行
  5. Power Appsに値を返す

前述のとおり、Power Appsからファイル識別子を受け取っています。

image.png

こちらからSharePointのファイル コンテンツを取得します。

image.png

base64関数で取得したファイルコンテンツを、文字列の base64 エンコード バージョンに変換します。これにより、Azure Functionsに渡す準備が整います。

image.png

入力
@{base64(outputs('ファイル_コンテンツの取得')?['body'])}

HTTP - Azure Functionsを実行

image.png

URIはデプロイしたリソースから取得します。
今回は環境変数で値を設定しています。

URI
@{parameters('AZURE_FUNCTION_ENDPOINT (gatsuo_AZURE_FUNCTION_ENDPOINT)')}?code=@{parameters('AZURE_SECRET (gatsuo_AZURE_SECRET)')}

Method POST

Headers
{
  "Content-Type": "application/json"
}
Body
{
  "file": "@{outputs('作成_base64')}"
}

設定から入出力をマスキングすることも可能です。

image.png

こちらを実行した結果がcontentとして取得できます。

image.png

response
@{body('HTTP')?['content']}

おわりに

構造化された文字列になるとAI活用の幅が広がります。
「Copilot活用のためにAzure Functionsを使いたいです!」は市民開発の立ち位置から打ち手として首を傾げるところですが、こういった技術が普及することにより、ファイルの形式関係なくAIとの対話ができそうなことにワクワクしますね。

今回もブログを読んでいただき、ありがとうございました!

7
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?