背景
GPT4.1 使うと、OCR 出来そうだったんで Logic Apps
で やってみた
OCR の精度自体は満足できるもので、Teams チャット用にできたら便利そうだった。
概要
Teams チャットに貼り付けた画像は、取得権限なさそうだったので、
Microsoft Lists へ Forms UI から投稿させて使うことにしてみた
- Forms UI で画像投稿
- 添付ファイル(画像も添付ファイル扱い)を取得
- 添付ファイルを、OpenAPI Action へ渡して テキスト抽出
- 投稿者に、チャットで通知
取得画像から、API 用 param の作成
以下の観点から、本当なら type
: image_url
に URL で渡す方が良い。
- Token 消費量
Storage を用意する必要があるので、今回は base64 にして渡すことにした。
ファイルサイズが大きい場合の対処も本来なら必要
content を複数ファイルで作っているので、そのまま渡す
type
: text
がもしあれば、変数の初期化時に設定しておく
あとがき
Teams チャットでできればもうちょい便利なんですけどね・・
最近 Graph API が制約厳しいので、添付ファイル取得 API とか出してくれないと、会社じゃ無理かな。
※HostedContents, Attachements を取得すればいいだけなので、
適切に権限設定出来るならオススメ
Code 例
{
"definition": {
"$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
"contentVersion": "1.0.0.0",
"triggers": {
"項目が作成されたとき": {
"type": "ApiConnection",
"inputs": {
"host": {
"connection": {
"name": "@parameters('$connections')['sharepointonline']['connectionId']"
}
},
"method": "get",
"path": "/datasets/@{encodeURIComponent(encodeURIComponent('https://{tenant.sharepoint.com}/personal/{username}'))}/tables/@{encodeURIComponent(encodeURIComponent('{list-id}'))}/onnewitems"
},
"recurrence": {
"interval": 3,
"frequency": "Minute"
},
"splitOn": "@triggerBody()?['value']"
}
},
"actions": {
"For_each": {
"type": "Foreach",
"foreach": "@body('添付ファイルの取得')",
"actions": {
"Append_to_array_variable": {
"type": "AppendToArrayVariable",
"inputs": {
"name": "Base64Images",
"value": {
"type": "image_url",
"image_url": {
"url": "data:@{body('添付ファイルのコンテンツの取得')['$Content-Type']};base64,@{body('添付ファイルのコンテンツの取得')['$Content']}"
}
}
},
"runAfter": {
"添付ファイルのコンテンツの取得": [
"SUCCEEDED"
]
}
},
"添付ファイルのコンテンツの取得": {
"type": "ApiConnection",
"inputs": {
"host": {
"connection": {
"name": "@parameters('$connections')['sharepointonline']['connectionId']"
}
},
"method": "get",
"path": "/datasets/@{encodeURIComponent(encodeURIComponent('https://{tenant.sharepoint.com}/personal/{username}'))}/tables/@{encodeURIComponent(encodeURIComponent('{list-id}'))}/items/@{encodeURIComponent(encodeURIComponent(triggerBody()?['ID']))}/attachments/@{encodeURIComponent(item()?['Id'])}/$value"
}
}
},
"runAfter": {
"Compose_Prompt": [
"Succeeded"
],
"添付ファイルの取得": [
"SUCCEEDED"
]
}
},
"項目の取得": {
"type": "ApiConnection",
"inputs": {
"host": {
"connection": {
"name": "@parameters('$connections')['sharepointonline']['connectionId']"
}
},
"method": "get",
"path": "/datasets/@{encodeURIComponent(encodeURIComponent('https://{tenant.sharepoint.com}/personal/{username}'))}/tables/@{encodeURIComponent(encodeURIComponent('{list-id}'))}/items/@{encodeURIComponent(triggerBody()?['ID'])}"
},
"runAfter": {
"Initialize_variables_Messages": [
"Succeeded"
]
}
},
"Initialize_variables_Messages": {
"type": "InitializeVariable",
"inputs": {
"variables": [
{
"name": "Base64Images",
"type": "array",
"value": [
{
"type": "text",
"text": "what's in this image?"
}
]
}
]
},
"runAfter": {}
},
"Creates_a_completion_for_the_chat_message-copy": {
"type": "ApiConnection",
"inputs": {
"host": {
"connection": {
"name": "@parameters('$connections')['azureopenai']['connectionId']"
}
},
"method": "post",
"body": {
"messages": [
{
"role": "system",
"content": "@{outputs('Compose_Prompt')}"
},
{
"role": "user",
"content": "@variables('Base64Images')"
}
],
"temperature": 1,
"top_p": 1,
"stream": false,
"stop": null,
"max_tokens": 15000,
"presence_penalty": 0,
"frequency_penalty": 0,
"n": 1,
"seed": 0,
"logprobs": false,
"response_format": {
"type": "text"
}
},
"path": "/2024-02-15-preview/deployments/@{encodeURIComponent('gpt-4.1-mini')}/chat/completions",
"queries": {
"api-version": "2025-04-01-preview"
}
},
"runAfter": {
"For_each": [
"Succeeded"
]
}
},
"For_each_いつも_[0]_とはいえ、面倒なのでこのまま_": {
"type": "Foreach",
"foreach": "@body('Creates_a_completion_for_the_chat_message-copy')['choices']",
"actions": {
"チャットまたはチャネルでメッセージを投稿する": {
"type": "ApiConnection",
"inputs": {
"host": {
"connection": {
"name": "@parameters('$connections')['teams']['connectionId']"
}
},
"method": "post",
"body": {
"recipient": "@triggerBody()?['Author']?['Email']",
"messageBody": "<p class=\"editor-paragraph\">@{items('For_each_いつも_[0]_とはいえ、面倒なのでこのまま_')?['message']?['content']}</p>"
},
"path": "/beta/teams/conversation/message/poster/Flow bot/location/@{encodeURIComponent('Chat with Flow bot')}"
}
},
"項目の更新": {
"type": "ApiConnection",
"inputs": {
"host": {
"connection": {
"name": "@parameters('$connections')['sharepointonline']['connectionId']"
}
},
"method": "patch",
"body": {
"Done": true
},
"path": "/datasets/@{encodeURIComponent(encodeURIComponent('https://{tenant.sharepoint.com}/personal/{username}'))}/tables/@{encodeURIComponent(encodeURIComponent('{list-id}'))}/items/@{encodeURIComponent(triggerBody()?['ID'])}"
},
"runAfter": {
"チャットまたはチャネルでメッセージを投稿する": [
"SUCCEEDED"
]
}
}
},
"runAfter": {
"Creates_a_completion_for_the_chat_message-copy": [
"Succeeded"
]
}
},
"Compose_Prompt": {
"type": "Compose",
"description": "抽出した文字をHTML構造で返してください(```html などは不要)。1.画像解析の基本\\n画像を解析する際は、走査線をイメージして解析してください。左上から右へ、走査線のように移動しつつ、右下まで解析を行います。\\n2.色の再現\\n画像内の色彩を正確に再現するために、使用されている色のパレットやトーンを明確に記述してください。具体的な色使いの説明を加え、色の変化や影響を考慮します。\\n3.構図の再現\\n画像の構図を意識し、要素の配置やバランスを考慮してください。視線の流れを意識し、重要な情報や要素を効",
"inputs": "@if(equals(triggerBody()?['Prompt'],null), '抽出した文字をHTML構造で返してください(```html などは不要)。1.画像解析の基本\n画像を解析する際は、走査線をイメージして解析してください。左上から右へ、走査線のように移動しつつ、右下まで解析を行います。\n2.色の再現\n画像内の色彩を正確に再現するために、使用されている色のパレットやトーンを明確に記述してください。具体的な色使いの説明を加え、色の変化や影響を考慮します。\n3.構図の再現\n画像の構図を意識し、要素の配置やバランスを考慮してください。視線の流れを意識し、重要な情報や要素を効果的に配置することで、視覚的なインパクトを高めます。\n4.表の再現\nデータを整理して視覚的にわかりやすくするために、表形式で情報を提示します。見出しやデータの配置、色使いに注意を払い、視認性を高めることを心がけます。\n', triggerBody()?['Prompt'])",
"runAfter": {
"項目の取得": [
"Succeeded"
]
}
},
"添付ファイルの取得": {
"type": "ApiConnection",
"inputs": {
"host": {
"connection": {
"name": "@parameters('$connections')['sharepointonline']['connectionId']"
}
},
"method": "get",
"path": "/datasets/@{encodeURIComponent(encodeURIComponent('https://{tenant.sharepoint.com}/personal/{username}'))}/tables/@{encodeURIComponent(encodeURIComponent('{list-id}'))}/items/@{encodeURIComponent(encodeURIComponent(triggerBody()?['ID']))}/attachments"
},
"runAfter": {
"Initialize_variables_Messages": [
"SUCCEEDED"
]
}
}
},
"outputs": {},
"parameters": {
"$connections": {
"type": "Object",
"defaultValue": {}
}
}
},
"parameters": {
"$connections": {
"type": "Object",
"value": {
"sharepointonline": {
"id": "/subscriptions/{subscription-id}/providers/Microsoft.Web/locations/northcentralus/managedApis/sharepointonline",
"connectionId": "/subscriptions/{subscription-id}/resourceGroups/chatbot_choji/providers/Microsoft.Web/connections/sharepointonline",
"connectionName": "sharepointonline"
},
"azureopenai": {
"id": "/subscriptions/{subscription-id}/providers/Microsoft.Web/locations/northcentralus/managedApis/azureopenai",
"connectionId": "/subscriptions/{subscription-id}/resourceGroups/chatbot_choji/providers/Microsoft.Web/connections/azureopenai-4",
"connectionName": "azureopenai-4"
},
"teams": {
"id": "/subscriptions/{subscription-id}/providers/Microsoft.Web/locations/northcentralus/managedApis/teams",
"connectionId": "/subscriptions/{subscription-id}/resourceGroups/chatbot_choji/providers/Microsoft.Web/connections/teams",
"connectionName": "teams"
}
}
}
}
}