📇

AIを使った名刺解析・登録Slackアプリのコードレシピ(Cloudflare / Azure OpenAI / FileMaker)

2025/02/17に公開

はじめに

筆者は技術検証を兼ねて、LLMを使った社内ツールを数多く作ってきました。使われたものもあれば、使われないものもあります。忘れられるものの方が多いです。

そのなかで好評だったのが、Slackを使った名刺登録アプリでした。

LLMのOCR機能を使ったもので、作例が多く、屋上屋を架すは否めませんが、この記事ではコンテキストを絞って、実際に運用しているCloudflare Workers、Azure OpenAI Service 、FileMakerを用いた例を、具体的なコード実装に焦点を当てて紹介します。

本記事のスコープ

  • TypeScriptによるコード実装上のポイントを主題とします。
    • 読者はTypeScriptでの開発に習熟している前提です。
  • Slackアプリの作成方法については記述しません。
  • Cloudflare、Azure OpenAIのセットアップについては記述しません。
  • Step by Stepでの開発手順は扱いません。下記のScrapも参考にしてください。

https://zenn.dev/hosaka313/scraps/2d5eabfb23eddc

サンプルリポジトリ

https://github.com/HosakaKeigo/Slack-BusinessCard-App-sample/tree/main

動作

  • Slackに写真を投稿

  • 解析し、FileMakerに登録

アーキテクチャ


Slack Appのアーキテクチャ

選定理由

Cloudflare Workers

  • デプロイが早く、開発体験が良い。
    • Firebase Functions / AWS ServerlessでSlack bot開発の経験があります。それらと比べると段違いにストレスがないです。

https://qiita.com/hosaka_/items/bbe777d04752cb6804c8

  • Cloudflare Workers用のライブラリが優れている
    • Edgeランタイム対応の心配なし。
    • 3秒以内にレスポンスを返さないといけない「3秒ルール」を、Cloudflare WorkersのwaitUntilを利用したLazy Listenerで回避できる。従来のライブラリだとPythonのみ対応していた。
    • 作者さんが「2023 年にTypeScriptでSlackアプリを作るなら、最も使いやすいライブラリと言っても過言ではないと思います。」[1]と述べている通り、後発で洗練されており、使いやすい。

https://github.com/seratch/slack-cloudflare-workers

https://zenn.dev/seratch/articles/c370cf8de7f9f5

  • コールドスタートが(ほぼ?)ない(または他者FaaSより短い)

欠点として、処理時間の制限(有料のWorkers Paidで解決可能)、同時リクエスト数の制限(Simultaneous open connections)が挙げられます。特に後者は複数枚の名刺をfetchで並列処理しようとすると問題になってきます。これはWorkers Paidでも緩和されません。1度に処理できる画像数を5枚に制限する運用で回避しています。

Hono

  • Cloudflare Workersとの親和性が高い。
  • @hono/zod-openapiを使ったAPI定義の開発体験が良い。
    • OpenAIへの処理とFileMakerへの処理の2つのエンドポイントを実装したかったため、生のWorkersではなくHonoを用いました。

Azure OpenAI Service

名刺画像を扱うので、エンタープライズレベルのAIベンダーとして採用。モデルはgpt-4o。OCR機能に関して、gpt-4-vision-previewの頃は物足りなさがありましたが、gpt-4o以降は日本語性能も向上しました。

現在であれば、Vertex AI経由のGemini 2.0 Flashも有力な選択肢です。将来的にはCloudflareのWorkers AIに期待です。

FileMaker

社内のデータベースがFileMakerであるため。APIのInterfaceに合わせれば任意のデータベースで応用可能です。

ポイント解説

実装上の要点を取り上げます。

Slackからのイベント受け取り

ファイル添付イベントは、eventのsubtypeがfile_shareかどうかで判定できます。

https://github.com/HosakaKeigo/Slack-BusinessCard-App-sample/blob/204e20d2777fa65be0a770937972196d5b0b7089/frontend/src/index.ts#L34-L37

subtypeの部分にはしっかり型補完が効きます。

ファイルはpayload.filesから取得できます。

画像をBase64に変換する

画像はCloudflare R2は使わず、Base64にしてバックエンドに渡す汎用的な方式を取っています。
payload.filesFileElement[]という型で、FileElementurl_private_download認証付きでfetchし、Base64に変換します。

https://github.com/HosakaKeigo/Slack-BusinessCard-App-sample/blob/204e20d2777fa65be0a770937972196d5b0b7089/frontend/src/helpers/processImage.ts#L18-L31

BufferはNodeのAPIなので、wrangler.tomlcompatibility_flags = ["nodejs_compat"]を指定する必要があります。

https://developers.cloudflare.com/workers/runtime-apis/nodejs/buffer/

また、認証用tokenはcontextから取得できます。

https://github.com/HosakaKeigo/Slack-BusinessCard-App-Sample/blob/037fa728e9e7d385a8cf9f58aba9dba24d9121c5/frontend/src/index.ts#L75

Workers間のリクエスト: Service Bindings

責務をスッキリさせるために、Slackとのやりとりを担当するWorkerと、OpenAI、FileMakerとの外部連携を行うHono.jsのWorkerの、2つを立てています。

Worker間のやり取りは、Service Bindingsを使うことでエンドポイントを公開せずにセキュアに行うことができます。

https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/

https://github.com/HosakaKeigo/Slack-BusinessCard-App-sample/blob/204e20d2777fa65be0a770937972196d5b0b7089/frontend/wrangler.toml#L13-L17

https://github.com/HosakaKeigo/Slack-BusinessCard-App-sample/blob/204e20d2777fa65be0a770937972196d5b0b7089/frontend/src/helpers/processImage.ts#L35-L46

ボタンの処理

すでにレコードがある場合は、ユーザーがボタンからレコードを作成できるようにしています。

ボタンの処理は、ボタンに付与したaction_idをlistenして行います。

ボタン要素にaction_idを付与。
https://github.com/HosakaKeigo/Slack-BusinessCard-App-sample/blob/204e20d2777fa65be0a770937972196d5b0b7089/frontend/src/helpers/slackBlocks.ts#L247-L270

受け取り。action.typeなどでNarrowingして型補完を効かせます。
https://github.com/HosakaKeigo/Slack-BusinessCard-App-sample/blob/204e20d2777fa65be0a770937972196d5b0b7089/frontend/src/index.ts#L150-L157

個人的なベストプラクティスとして、action_idはリテラルではなく、Enumかas constで型を縛ったオブジェクトを作って管理するようにしています。

もし処理に3秒以上かかる場合は、引数を3つ渡してあげて、3つ目に時間のかかる処理を記述すればOKです。(従来のBolt for JavaScriptで開発していた人ならわかると思うのですが、これは感動的なことです。)

https://github.com/HosakaKeigo/Slack-BusinessCard-App-sample/blob/204e20d2777fa65be0a770937972196d5b0b7089/frontend/src/index.ts#L225-L230

SlackのUIの実装: Block Kit Builder + LLM

Slackが提供しているBlock Kit Builderで作りたいイメージを作り、LLMに細部を整えてもらうとスムーズに実装できます。

https://app.slack.com/block-kit-builder

このアプリでは以下のファイルで定義されています。

https://github.com/HosakaKeigo/Slack-BusinessCard-App-sample/blob/19c4b2fa3d967fc7152a98a64903a9c10c0f3dc2/frontend/src/helpers/slackBlocks.ts


以下、Hono.jsの2つ目のWorkerに移ります。

Azure OpenAIによる名刺のJSON化: Structured Outputs

所望の形式のJSONを安定して返すためにStructured Outputsを用います。

https://platform.openai.com/docs/guides/structured-outputs?lang=javascript

具体的なコード例は下記です。beta.chat.completions.parseメソッドを使うのがポイントです。

openai/helpers/zodのhelperを用いれば、OpenAPIスキーマの代わりにZodスキーマを渡せます。(ただし、複雑なスキーマは不可)

https://github.com/HosakaKeigo/Slack-BusinessCard-App-sample/blob/19c4b2fa3d967fc7152a98a64903a9c10c0f3dc2/backend/src/services/openAIService.ts#L21-L50

https://github.com/HosakaKeigo/Slack-BusinessCard-App-sample/blob/19c4b2fa3d967fc7152a98a64903a9c10c0f3dc2/backend/src/types.ts#L16-L64

プロンプトは以下のようになっています。Zodスキーマでカバーできない細かい要求を追加するのが良いと思います。
筆者のケースでは日本語の名刺で漢字の読みがわからないケースがあったので、可能であればメールアドレスから推測するように指示を行っています。

https://github.com/HosakaKeigo/Slack-BusinessCard-App-sample/blob/19c4b2fa3d967fc7152a98a64903a9c10c0f3dc2/backend/src/prompts.ts#L1-L16

FileMakerの処理

FileMaker Data APIを用いることになります。自前実装しても良いですが、以下のライブラリを用いています。

https://github.com/proofgeist/fmdapi

理由は下記です。

具体的には、以下の実装になります。

https://github.com/HosakaKeigo/Slack-BusinessCard-App-sample/blob/19c4b2fa3d967fc7152a98a64903a9c10c0f3dc2/backend/src/services/fileMakerService.ts#L8-L41

sessionトークンをCloudflare KVを使って共有することで、過度にsessionが発行されないようになっています。

なお、codegen機能では以下のスキーマが生成されます。

https://github.com/HosakaKeigo/Slack-BusinessCard-App-sample/blob/19c4b2fa3d967fc7152a98a64903a9c10c0f3dc2/backend/src/schema/NameCard.ts#L8-L30

型が付くのでPrismaのような開発感でFileMaker処理を実装できます。

おわりに

Zapierやmakeなどノーコードツールで実装する選択肢もある名刺処理ですが、コードを書いて構築する場合の一例を示しました。

あえてコードを書く理由は、細かい調整が効くことの他に、LLMとの親和性が高いためです。
すでにLLMのコーディング力はかなりのものになっているため、特定のノーコードツールのイディオム(語法)に依存するより、LLMの助けを享受しやすいTypeScriptなどのコードで書いてしまった方が、将来的なメンテ・機能拡張のコストが低くなるケースが増えると考えています。(ユースケースによるのはいうまでもありませんが。)

Slackアプリの開発に関しては、繰り返しになりますが、Cloudflareのデプロイ・ライブラリの開発体験が非常に良いです。ぜひ試してみてください。

脚注
  1. https://zenn.dev/seratch/articles/c370cf8de7f9f5 ↩︎

Discussion