7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

FileMakerAdvent Calendar 2023

Day 20

OpenAI APIをFileMakerから叩いて遊ぶ(JSONモード/Vision API(画像認識)/DALL-Eの画像生成)

Last updated at Posted at 2023-12-19

はじめに

本記事はQiita Advent Calendar 2023 の XXX Advent Calendar 2023 n日目記事です。」と言ってみたい一心で書かれた本記事は、Qiita Advent Calendar 2023FileMaker Advent Calendar 2023 20日目記事です。

技術顧問@Hi_NoguchiがFileMakerで手を叩いていたので、わたしはFileMakerでOpenAI APIを叩いて1みることにします。

なお、FileMakerでOpenAI APIを叩いた先行記事は下記などいくつかありますのでご参考ください。

JSONモード!、画像生成!とわざとらしくタイトルに入れたのは新規性のための窮余の一策であります。

成果物

GitHubにて公開しました。(2024/03/28追記)

前提

  • Claris FileMaker Pro ver.19.6.3
  • ChatGPT Plus
    • OpenAI APIキー取得済み

目標

  • FileMakerで下記のOpenAI APIを叩いて遊ぶ
    • Chat Completions
      • JSONモード
    • gpt-4-vision-previewによる画像認識
    • DALL·Eによる画像生成

最終的なスクリプトは下記のようになります。

├── lib
│   ├── convertToArray($values)
│   ├── createUserMessage($message)
│   ├── createSystemMessage($message)
│   ├── createRequest($body)
│   ├── parseResponse($response)
│   ├── setJSON_Mode
│   └── setupVisionAPI
├── openai
│   ├── /chat/completions
│   └── /images/generations
└── config
    ├── globals
    │   ├── global: OpenAI_APIKey($get)
    │   └── global: setOpenAIEndpoints($method)
    └── setDefaultValues

0. レイアウトを作る

まずは、レイアウトを適当に作成します。
image.png

  • モデル選択(値一覧)
  • システムプロンプト
  • ユーザープロンプト
  • レスポンス
  • 画像欄(オブジェクトフィールド)
  • 関数実行ボタン

あたりを置きました。

1. ChatCompletionを叩いて遊ぶ

さっそくFileMakerスクリプトを書いていきます。

「URLから挿入」で正しいエンドポイントに正しいヘッダと正しいペイロードを送れば動くでしょう。

1-1. config作成

まず、config周りを作成します。

OpenAI APIキーの取得

OpenAI_APIKey($get)

たぶんグローバル変数にしない方が良いですが、めんどくさいのでグローバリズムを唱えました。2ここにお手持ちのAPIキーをセットしてください。

エンドポイントの取得関数

一元管理するために、setOpenAIEndpoints($method)を作成します。
image.png

デフォルト値の設定

ついでに、言っても聞かない人々を想定して、デフォルト値を設定するsetDefaultValuesを作成しました。

image.png

デフォルトのシステムプロンプトは下記にしました。

"You are a helpful, respectful and honest assistant. Always answer as helpfully as possible, while being safe. Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. Please ensure that your responses are socially unbiased and positive in nature.If a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don't know the answer to a question, please don't share false information. Remember answer in the language you're asked in (mostly in Japanese)"

MetaのLlamaのdefault system promptをお借りしたのですが、いまは削除されていました。

「お利口にするんだよ」ということを現代風に書けば良いと思います。

1-2. Utility関数の作成

OpenAI APIのペイロードを作成するための便利関数たちを作成します。

completionsでのペイロードのスキーマは以下。これを作ります。

 "messages": [
      {
        "role": "system",
        "content": "You are a helpful assistant."
      },
      {
        "role": "user",
        "content": "Who won the world series in 2020?"
      }
    ]

createSystemMessage($message)

引数にstringを取り、

   {
        "role": "system",
        "content": $message
   }

を返す関数です。

image.png

後で改行区切り値として引数に渡すので、改行をSubstituteしています。

createUserMessage($message)

同様に

   {
        "role": "user",
        "content": $message
   }

となるJSONを作成します。

image.png

gpt-4-vision-previewを使った画像認識の場合の細工をしていますが、これは後述します。まずはElse内の分岐を用います。

convertToArray($values)

最後にJSONたちを配列にする関数を作成します。

FileMakerで配列を作成するベストプラクティスがわからなかったので、値一覧を引数にとり、JSONSetElementを使う方法にしました。配列もJSONです。

image.png

createRequest($body)

いよいよ、といきたいところですが、我々の未来のためにOpenAIへリクエストを行う責務を担うcreateRequestを作成しておきます。こんな感じ。

image.png

$optionsは下記です。

"-d  @$body " & " -H \"Authorization: Bearer " & $$openai_api_key & "\" " & "-H \"Content-Type: application/json\" "

参考

これで、準備完了です。

1-3. OpenAI APIを叩く

メインスクリプトになる/chat/completitionsを作成します。

セットアップ

入力Validation、エンドポイント取得を行います。

JSON Modeでは一手間必要ですが、後のお楽しみです。
image.png

ペイロード作成

image.png

準備した関数たちを活躍させます。

最後にmodelやその他オプションを足してペイロードの完成です。

JSONSetElement ( "" ; 
  [ "model" ; OpenAICaller::model ; JSONString ];
  [ "messages" ; $messages ; JSONArray ]
)

{
  "model": ${model},
  "messages": [
      {
        "role": "system",
        "content": ${system_prompt}
      },
      {
        "role": "user",
        "content": ${user_prompt}
      }
    ]
}

APIリクエスト

image.png
IfブロックはJSONモードと画像認識の際の細工で、いまは関係ありません。

createRequest($body)にペイロードをわたし、フィールド設定します。

これでこの関数を叩いてみます。

アドカレのことを思っていたらカレーが食べたくなったので、カレーの作り方を聞きました。

image.png

あの見慣れたOpenAI APIのJSONが返ってきました。おかえりJSON!

{
  "id": "chatcmpl-8X8hZ6E5E80NEqweIfHUEPkQ2GK1R",
  "object": "chat.completion",
  "created": 1702908965,
  "model": "gpt-3.5-turbo-0613",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "カレーライスの作り方はいくつかのバリエーションがありますが、以下は基本的な手順です。\n\n1. まず、肉や野菜を切ります。一般的に、牛肉や鶏肉を使いますが、野菜だけを使うバージョンもあります。好みに応じて、じゃがいも、ニンジン、タマネギ、ピーマンなどの野菜も切りましょう。\n\n2. 大きめの鍋に油を熱し、肉を炒めます。肉に火が通って軽く焼き色がついたら、野菜を加えて炒めます。\n\n3. 野菜がしんなりしてきたら、カレールーを加えます。一般的に市販のカレールーを使いますが、自家製のルーを使うこともできます。\n\n4. カレールーを加えたら、水や出汁を少しずつ加えながら、ルーが溶けるまでかき混ぜます。カレーソースがとろみがつくまで、弱火で煮込みます。\n\n5. カレーソースの味を調整します。塩やこしょうで味を整え、辛さが好みならチリパウダーや唐辛子を加えることもできます。\n\n6. 完成したカレーソースをご飯の上にかけて、お好みでトッピングを加えましょう。お好みで、目玉焼きやフライドオニオン、ピクルスなどを添えることもできます。\n\n以上が基本的なカレーライスの作り方です。好みに合わせてカスタマイズしてみてください。"
      },
      "logprobs": null,
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 139,
    "completion_tokens": 519,
    "total_tokens": 658
  },
  "system_fingerprint": null
}

これでも良いですが、せっかくなのでparserを作ります。

Parserを作る

親の顔よりも見たchoices[0].message.contentをFileMakerで行います。

実装は簡単で、JSONGetElementに親の顔よりも見たchoices[0].message.contentを入れるだけです。

parseResponse($response)
image.png

これを先のスクリプトのフィールド設定の手前に加えれば、カレーの作り方だけ取得できます。

カレーライスの作り方はいくつかのバリエーションがありますが、以下は基本的な手順です。

1. まず、肉や野菜を切ります。一般的に、牛肉や鶏肉を使いますが、野菜だけを使うバージョンもあります。好みに応じて、じゃがいも、ニンジン、タマネギ、ピーマンなどの野菜も切りましょう。

2. 大きめの鍋に油を熱し、肉を炒めます。肉に火が通って軽く焼き色がついたら、野菜を加えて炒めます。

3. 野菜がしんなりしてきたら、カレールーを加えます。一般的に市販のカレールーを使いますが、自家製のルーを使うこともできます。

4. カレールーを加えたら、水や出汁を少しずつ加えながら、ルーが溶けるまでかき混ぜます。カレーソースがとろみがつくまで、弱火で煮込みます。

5. カレーソースの味を調整します。塩やこしょうで味を整え、辛さが好みならチリパウダーや唐辛子を加えることもできます。

6. 完成したカレーソースをご飯の上にかけて、お好みでトッピングを加えましょう。お好みで、目玉焼きやフライドオニオン、ピクルスなどを添えることもできます。

以上が基本的なカレーライスの作り方です。好みに合わせてカスタマイズしてみてください。

モデルをgpt-4-1106-previewにして同じ質問をしてみます。

image.png

カレーライスを作る基本的な方法をご紹介します。以下は4人分の材料と手順です。

【材料】
- 玉ねぎ:2個(みじん切り)
- にんじん:1本(乱切り)
- じゃがいも:2個(一口大に切る)
- 肉(牛肉または豚肉または鶏肉):300g(一口大に切る)
- サラダ油:適量
- 水:約800ml
- カレー粉:大さじ1(お好みで調整)
- カレールー:1箱(約100g〜200g)
- 塩:少々(味を調整するため)
- ご飯:適量

【作り方】
1. 鍋にサラダ油を熱し、玉ねぎを中火で炒める。玉ねぎが透明感を帯びてきたら、肉を加えて炒める。

2. 肉の色が変わったら、にんじんとじゃがいもを加え、さらに炒める。

3. 野菜と肉が炒まったら、水を加えて中火で煮込む。水が沸騰したらアクを取り除きながら、弱火で20分程煮込む。

4. 野菜が柔らかくなったら、火を止めてカレールーを入れる。この時、カレールーを入れる前に一度火を止めることで、ダマになりにくくなります。

5. カレールーが完全に溶けたら、再び火をつけて弱火〜中火で10分程度煮込む。この間にカレーの味がなじみます。

6. 最後にカレー粉を加え、塩で味を調整し、数分煮込んで完成です。

7. ごはんを皿に盛り、上からカレーをかけてサーブします。

カレーは好みに応じて、りんごをすりおろして入れたり、チャツネやヨーグルトを加えたり、辛さを調節したりすることができます。具材やスパイスを変えることで、様々なバリエーションが楽しめます。

カレールーの後にカレー粉を入れる、ちょっとわたしには及ばない世界に行ってしまいました。

2. JSONモードで遊ぶ

前準備が長くなってしまったので、急いでタイトル回収するためにJSONモードで叩いて遊びます。

JSONモードをOnにすると、常にJSONのみを返してくれます。

何が素晴らしいかというと、下記のような苦しみがなくなります。

わたし「JSONで返して」

ChatGPTさん「はい、以下に条件を満たすJSONデータを示します...」

JSON.parseさん「Error: Unexpected token 'は', "はい、以下に条件を満"... is not valid JSON」

わたし「WHY」

ということでJSONモード、やっていきましょう。

ポイント

  • modelはgpt-4-1106-preview または gpt-3.5-turbo-1106
  • リクエストのペイロードにresponse_format: { type: "json_object" }を追加する。
  • システムプロンプトなどに必ずJSONで回答する旨を入れる。

When using JSON mode, always instruct the model to produce JSON via some message in the conversation, for example via your system message.
(https://platform.openai.com/docs/guides/text-generation/json-mode)

実装

以上のポイントをふまえて、実装します。

フィールド追加

is_json_modeというフィールドを追加しました。

JSONモードはモデルが限定されているので、モデルの値によってオブジェクトを隠すようにすると良いでしょう。
image.png

スクリプト修正

下記のようにsetJSON_Modeを作成します。
image.png

  • response_format: { type: "json_object" }の作成
  • JSONの文言が入っているかvalidation

を行なっています。

これをCompletionのスクリプトに組み込みます。

image.png

リクエストの前にresponse_format$bodyに差し込みます。
image.png

これでJSONモードが使えるようになりました。

遊ぶ

まだカレーが食べたいので、カレーの作り方をJSONモードをOnにして聞いてみました。
image.png

"your_witty_comments_about_curry"なる、ふざけたプロパティを指定して、しっかりフォーマットを守れるか検証します。

回答

{
  "ingredients": [
    "玉ねぎ(中)2個",
    "にんじん1本",
    "じゃがいも2個",
    "鶏肉(もしくは肉の種類お好みで)200g",
    "カレールウ(市販のもの)1箱",
    "水適量",
    "サラダ油大さじ2",
    "塩適量",
    "ごはん適量"
  ],
  "instructions": [
    "玉ねぎは薄切りに、にんじんとじゃがいもは一口大に切る。",
    "鶏肉は一口大に切って、塩で下味をつける。",
    "鍋に油を熱し、玉ねぎを中火で炒める。",
    "玉ねぎが透明になったら、鶏肉を加えて炒める。",
    "鶏肉に火が通ったら、にんじんとじゃがいもを加えて炒める。",
    "野菜がしんなりしたら、水を加えて煮込む。",
    "水が沸騰したらアクを取り、火を弱めてふたをし、20分程度野菜がやわらかくなるまで煮る。",
    "野菜が柔らかくなったらカレールウを少しずつ割って加え、溶かし込む。",
    "ルウが完全に溶けたら中火に戻して数分煮る。好みで塩味を調える。",
    "ごはんを皿に盛り、その上にカレーをかける。"
  ],
  "your_witty_comments_about_curry": "カレーは無限の可能性を秘めた料理の宝石箱です。具材を変えたり、スパイスを加えたアレンジ一つで、あなた好みの世界に一つだけのカレーが出来上がります。ベースとなるレシピをマスターしたら、あとは遊び心と好奇心を大いに活かして、様々なカレーの世界を探検してみてください。"
}

要望通りのフォーマットで返してくれました。

ここで酒を飲みます」といった、某バズレシピお兄さんのようなおかしなinstruction3が紛れ込むこともありません。

3. Vision API(画像認識)で遊ぶ

続いて、画像認識を試してみます。こちらもポイントから。

ポイント

  • 執筆時点でモデルは"gpt-4-vision-preview"のみ
  • userプロンプトに"type": "text""type": "image_url"を加える。
    • "image_url"を指定
    • Base64も可
  • max_tokensを指定
    • 指定しないと回答が途切れる。
sample_vision_request.json
{
    "model": "gpt-4-vision-preview",
    "messages": [
      {
        "role": "user",
        "content": [
          {
            "type": "text",
            "text": "What’s in this image?"
          },
          {
            "type": "image_url",
            "image_url": {
              "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg"
            }
          }
        ]
      }
    ],
    "max_tokens": 300
}

https://platform.openai.com/docs/guides/vision

実装

ポイントをふまえて、実装していきます。画像はURLで渡す形式とします。

フィールド追加

image_urlを追加します。こちらもモデルの値に応じてオブジェクトを隠すと良いでしょう。
image.png

スクリプト修正

createUserMessage($message)

Vison APIではペイロードの形が異なるので調整します。
image.png

変数を設定の中身を示します。

Let (  
 [
    $text = JSONSetElement ( "" ; 
      [ "type" ; "text" ; JSONString ];
      [ "text" ; $message ; JSONString ]
    );
    $image_url = JSONSetElement ( "" ; ["url" ; OpenAICaller::image_url ; JSONString ]; [ "detail" ; "high" ; JSONString] );
    $image = JSONSetElement ( "" ; 
      [ "type" ; "image_url" ; JSONString ];
      [ "image_url" ; $image_url ; JSONObject ]
    );
    $array = "[" & $text & "," & $image & "]"
  ];

  JSONSetElement ( "" ; 
   ["role" ; "user" ; JSONString ];
   ["content" ; $array ; JSONArray ]
  )
)

setVisionAPIは、モデルを決め打ちで設定しているだけ。なくても構いません。
image.png

/chat/completitions

$bodyを作った後にimage_urlが入っている場合は、max_tokenに設定します。

ベタ打ちで800にしましたが、configやフィールドで管理するのが良さそうです。
image.png

遊ぶ

検証画像として、カレーの画像を使います。

豚バラで作るコクうまポークカレー
https://housefoods.jp/recipe/rcp_00025463.html

image.png

こちらの画像のURLをフィールドにセットしてスクリプトを実行してみます。

image.png

こちらが回答。福神漬け..?

この画像は、具だくさんのおいしそうなカレーライスを写したものです。日本の家庭料理としてポピュラーな一皿で、温かいご飯の上に濃厚でスパイシーなカレーソースがたっぷりかかっています。カレーには肉や野菜がゴロゴロ入っており、色とりどりの具材が食欲をそそります。トッピングや隣に添えられたピクルスや福神漬けなどがあり、これらのアクセントがカレーの風味を引き立てていることでしょう。

Wikipediaから、けっこう有名な部類のホモ・サピエンスの画像を試してみます。

https://upload.wikimedia.org/wikipedia/commons/archive/6/6a/20200513060036%21Johann_Sebastian_Bach.jpg

回答はこちら。

この画像は、バロック時代の偉大な作曲家、ヨハン・セバスティアン・バッハの肖像画です。高解像度の写真で、その細部に至るまで精巧に描かれています。バッハは貴族的な姿で描かれており、豊かな装飾が施された服を着ています。彼の表情は深い思索にふけっているようで、彼の音楽の複雑さと緻密さを反映しているかのようです。

洒落た一言を追加してくれました。バッハの名前はURLの文字列でバレているかもしれません。

弊協会(ピティナ)のキャラクター「ぴてぃにゃん」だとどうなるでしょうか。4
image.png
https://corporate.piano.or.jp/ptnyan/index.html

画像には、ピアノをモチーフにしたキャラクターが描かれています。背景は主にピンク色を基調としており、キャラクターは白と黒の猫で、ピアノの鍵盤をイメージしたデザインの服を着ています。表情は優しく愛らしく、音符やピアノにちなんだ小物も描かれていることから、音楽やピアノに関連するキャラクターであることが想像されます。商業的な使用を想定したグッズやプロモーション素材などに使用されるかもしれません。また、このキャラクターが何を象徴しているか、またはどのようなストーリーがあるのかは画像からは明確ではありませんが、ポップでキュートな雰囲気が感じられます。

まずますです。まさにいまプロモーション素材などに使用された、ぴてぃにゃんであります。

...と、これらはわりとうまくいったケースをチェリー・ピッキングしています。URLをヒントにしている雰囲気も感じます。

元の画像の画質が良くない場合など、ファンタジーあふれる頓珍漢な回答が来るケースも多かったです。

4. DALL-Eの画像生成で遊ぶ

最後に画像生成で遊びます。例によってポイントのおさらいから。

ポイント

  • エンドポイントは/images/generations
    • Chapter 1で設定済み。
  • モデルはdall-e-3/dall-e-2
  • OpenAI側でリクエスト後にシステムプロンプトが追加される。
  • サンプルのリクエストは下記。
sample_image_generation.json
{
    "model": "dall-e-3",
    "prompt": "a white siamese cat",
    "n": 1,
    "size": "1024x1024"
}

https://platform.openai.com/docs/guides/images/introduction?context=node

実装

レイアウト修正

  • 画像生成系モデルの場合は上記のスクリプトを実行するボタンを表示するようにしました。
    image.png

/images/generationsスクリプトの作成

image.png

画像生成といっても渡すものは多くなく、プロンプトも単一の"prompt"のみなため、シンプルに実装できます。

$body部分は下記の通り設定しています。

JSONSetElement ( "" ; 
  [ "model" ; OpenAICaller::model ; JSONString ];
  [ "prompt" ; $prompt ; JSONString ]
)

以下がポイントです。

  • OpenAIからのレスポンスの型がcompletionと異なる。data[0].urlで画像のURLを取得。
  • 画像のURLに対し、URLから挿入を行い、オブジェクトフィールドに保管

遊ぶ

まだカレーが食べたいので、カレーの画像を作成します。
image.png

結果がこちらです。あんまり美味しそうではない...?カレー、箸で食うん?

image.png

responseを示します。revised_promptの通り、プロンプトは変換されてから画像になります。

curry_image_response.json
{
  "created": 1702919194,
  "data": [
    {
      "revised_prompt": "Make an image reminiscent of a watercolor painting showcasing a delectable curry full of ingredients. This aromatic and tantalizing dish appears to burst with a plethora of spices, vegetables, and proteins, making you feel a comforting warmth just by looking at it. The loose, expressive brushstrokes of a watercolor style add a certain charm to the dish, accentuating the fragility and vibrancy of the ingredients. The curry should appear appetizingly on a white plate, contrasting with the hot, bubbly golden sauce. Don\u2019t forget the fluffy mound of rice, ready to soak up the curry goodness.",
      "url": "https://oaidalleapiprodscus.blob.core.windows.net/private/org-3qVKD0q..."
    }
  ]
}

水彩画を抜いてトライ。なんか違う・・・
image.png

じっくり煮込んだバターチキンカレーも注文してみました。
image.png

これはおいしそうですね。

おわりに

以上、FileMakerでOpenAI APIを叩いて遊んでみました。

中間APIを立ててそこでリクエスト組み立て・レスポンスparseしたほうが楽では...と思わないこともないですが、FileMaker16でJSON関数が追加されたので、リクエストを送るのはそれほど困難ではなかったです。

オブジェクトフィールドに生成画像を突っ込めたり、UIが簡単に作れるのはFileMakerの魅力だなと感じました。

カレー食べたい。5

  1. APIを「叩く」という表現、ちょっと暴力的ですね。本記事では肩をトントンとするように、APIは優しく叩くことを心がけています。

  2. argにgetを取っているのは、グローバル引数のgetterとsetterを引数で切り替えている癖です。今回はとくに意味はありません。

  3. https://www.youtube.com/watch?v=BcKNwo7bzJ4&t=188s

  4. 余談ですが、壁紙かわいいのでダウンロードしてください -> https://corporate.piano.or.jp/ptnyan/index.html

  5. 筆者は翌日カレーを食べた。

7
2
3

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
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?