AIを使った名刺解析・登録Slackアプリのコードレシピ(Cloudflare / Azure OpenAI / FileMaker)
はじめに
筆者は技術検証を兼ねて、LLMを使った社内ツールを数多く作ってきました。使われたものもあれば、使われないものもあります。忘れられるものの方が多いです。
そのなかで好評だったのが、Slackを使った名刺登録アプリでした。
LLMのOCR機能を使ったもので、作例が多く、屋上屋を架すは否めませんが、この記事ではコンテキストを絞って、実際に運用しているCloudflare Workers、Azure OpenAI Service 、FileMakerを用いた例を、具体的なコード実装に焦点を当てて紹介します。
本記事のスコープ
- TypeScriptによるコード実装上のポイントを主題とします。
- 読者はTypeScriptでの開発に習熟している前提です。
- Slackアプリの作成方法については記述しません。
- Cloudflare、Azure OpenAIのセットアップについては記述しません。
- Step by Stepでの開発手順は扱いません。下記のScrapも参考にしてください。
サンプルリポジトリ
動作
- Slackに写真を投稿
- 解析し、FileMakerに登録
アーキテクチャ
Slack Appのアーキテクチャ
選定理由
Cloudflare Workers
- デプロイが早く、開発体験が良い。
- Firebase Functions / AWS ServerlessでSlack bot開発の経験があります。それらと比べると段違いにストレスがないです。
- Cloudflare Workers用のライブラリが優れている
- Edgeランタイム対応の心配なし。
- 3秒以内にレスポンスを返さないといけない「3秒ルール」を、Cloudflare Workersの
waitUntil
を利用したLazy Listenerで回避できる。従来のライブラリだとPythonのみ対応していた。 - 作者さんが「2023 年にTypeScriptでSlackアプリを作るなら、最も使いやすいライブラリと言っても過言ではないと思います。」[1]と述べている通り、後発で洗練されており、使いやすい。
- コールドスタートが(ほぼ?)ない(または他者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かどうかで判定できます。
subtypeの部分にはしっかり型補完が効きます。
ファイルはpayload.files
から取得できます。
画像をBase64に変換する
画像はCloudflare R2は使わず、Base64にしてバックエンドに渡す汎用的な方式を取っています。
payload.files
はFileElement[]
という型で、FileElement
のurl_private_download
に認証付きでfetchし、Base64に変換します。
Buffer
はNodeのAPIなので、wrangler.toml
にcompatibility_flags = ["nodejs_compat"]
を指定する必要があります。
また、認証用tokenはcontext
から取得できます。
Workers間のリクエスト: Service Bindings
責務をスッキリさせるために、Slackとのやりとりを担当するWorkerと、OpenAI、FileMakerとの外部連携を行うHono.jsのWorkerの、2つを立てています。
Worker間のやり取りは、Service Bindingsを使うことでエンドポイントを公開せずにセキュアに行うことができます。
ボタンの処理
すでにレコードがある場合は、ユーザーがボタンからレコードを作成できるようにしています。
ボタンの処理は、ボタンに付与したaction_id
をlistenして行います。
ボタン要素にaction_id
を付与。
受け取り。action.type
などでNarrowingして型補完を効かせます。
個人的なベストプラクティスとして、action_id
はリテラルではなく、Enumかas const
で型を縛ったオブジェクトを作って管理するようにしています。
もし処理に3秒以上かかる場合は、引数を3つ渡してあげて、3つ目に時間のかかる処理を記述すればOKです。(従来のBolt for JavaScriptで開発していた人ならわかると思うのですが、これは感動的なことです。)
SlackのUIの実装: Block Kit Builder + LLM
Slackが提供しているBlock Kit Builderで作りたいイメージを作り、LLMに細部を整えてもらうとスムーズに実装できます。
このアプリでは以下のファイルで定義されています。
以下、Hono.jsの2つ目のWorkerに移ります。
Azure OpenAIによる名刺のJSON化: Structured Outputs
所望の形式のJSONを安定して返すためにStructured Outputsを用います。
具体的なコード例は下記です。beta.chat.completions.parse
メソッドを使うのがポイントです。
openai/helpers/zod
のhelperを用いれば、OpenAPIスキーマの代わりにZodスキーマを渡せます。(ただし、複雑なスキーマは不可)
プロンプトは以下のようになっています。Zodスキーマでカバーできない細かい要求を追加するのが良いと思います。
筆者のケースでは日本語の名刺で漢字の読みがわからないケースがあったので、可能であればメールアドレスから推測するように指示を行っています。
FileMakerの処理
FileMaker Data APIを用いることになります。自前実装しても良いですが、以下のライブラリを用いています。
理由は下記です。
- TypeScriptとの統合
-
codegen
機能を使えば、FileMakerのレイアウトのZodスキーマを生成できる
-
- Edgeランタイムで使える
具体的には、以下の実装になります。
sessionトークンをCloudflare KVを使って共有することで、過度にsessionが発行されないようになっています。
なお、codegen機能では以下のスキーマが生成されます。
型が付くのでPrismaのような開発感でFileMaker処理を実装できます。
おわりに
Zapierやmakeなどノーコードツールで実装する選択肢もある名刺処理ですが、コードを書いて構築する場合の一例を示しました。
あえてコードを書く理由は、細かい調整が効くことの他に、LLMとの親和性が高いためです。
すでにLLMのコーディング力はかなりのものになっているため、特定のノーコードツールのイディオム(語法)に依存するより、LLMの助けを享受しやすいTypeScriptなどのコードで書いてしまった方が、将来的なメンテ・機能拡張のコストが低くなるケースが増えると考えています。(ユースケースによるのはいうまでもありませんが。)
Slackアプリの開発に関しては、繰り返しになりますが、Cloudflareのデプロイ・ライブラリの開発体験が非常に良いです。ぜひ試してみてください。
Discussion