はじめに
「本記事はQiita Advent Calendar 2023 の XXX Advent Calendar 2023 n日目記事です。」と言ってみたい一心で書かれた本記事は、Qiita Advent Calendar 2023 の FileMaker 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による画像生成
- Chat Completions
最終的なスクリプトは下記のようになります。
├── 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. レイアウトを作る
- モデル選択(値一覧)
- システムプロンプト
- ユーザープロンプト
- レスポンス
- 画像欄(オブジェクトフィールド)
- 関数実行ボタン
あたりを置きました。
1. ChatCompletionを叩いて遊ぶ
さっそくFileMakerスクリプトを書いていきます。
「URLから挿入」で正しいエンドポイントに正しいヘッダと正しいペイロードを送れば動くでしょう。
1-1. config作成
まず、config周りを作成します。
OpenAI APIキーの取得
OpenAI_APIKey($get)
たぶんグローバル変数にしない方が良いですが、めんどくさいのでグローバリズムを唱えました。2ここにお手持ちのAPIキーをセットしてください。
エンドポイントの取得関数
一元管理するために、setOpenAIEndpoints($method)
を作成します。
デフォルト値の設定
ついでに、言っても聞かない人々を想定して、デフォルト値を設定するsetDefaultValues
を作成しました。
デフォルトのシステムプロンプトは下記にしました。
"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
}
を返す関数です。
後で改行区切り値として引数に渡すので、改行をSubstituteしています。
createUserMessage($message)
同様に
{
"role": "user",
"content": $message
}
となるJSONを作成します。
gpt-4-vision-preview
を使った画像認識の場合の細工をしていますが、これは後述します。まずはElse内の分岐を用います。
convertToArray($values)
最後にJSONたちを配列にする関数を作成します。
FileMakerで配列を作成するベストプラクティスがわからなかったので、値一覧を引数にとり、JSONSetElement
を使う方法にしました。配列もJSONです。
createRequest($body)
いよいよ、といきたいところですが、我々の未来のためにOpenAIへリクエストを行う責務を担うcreateRequest
を作成しておきます。こんな感じ。
$options
は下記です。
"-d @$body " & " -H \"Authorization: Bearer " & $$openai_api_key & "\" " & "-H \"Content-Type: application/json\" "
参考
これで、準備完了です。
1-3. OpenAI APIを叩く
メインスクリプトになる/chat/completitions
を作成します。
セットアップ
入力Validation、エンドポイント取得を行います。
ペイロード作成
準備した関数たちを活躍させます。
最後にmodelやその他オプションを足してペイロードの完成です。
JSONSetElement ( "" ;
[ "model" ; OpenAICaller::model ; JSONString ];
[ "messages" ; $messages ; JSONArray ]
)
↓
{
"model": ${model},
"messages": [
{
"role": "system",
"content": ${system_prompt}
},
{
"role": "user",
"content": ${user_prompt}
}
]
}
APIリクエスト
IfブロックはJSONモードと画像認識の際の細工で、いまは関係ありません。
createRequest($body)
にペイロードをわたし、フィールド設定します。
これでこの関数を叩いてみます。
アドカレのことを思っていたらカレーが食べたくなったので、カレーの作り方を聞きました。
あの見慣れた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
を入れるだけです。
これを先のスクリプトのフィールド設定の手前に加えれば、カレーの作り方だけ取得できます。
カレーライスの作り方はいくつかのバリエーションがありますが、以下は基本的な手順です。
1. まず、肉や野菜を切ります。一般的に、牛肉や鶏肉を使いますが、野菜だけを使うバージョンもあります。好みに応じて、じゃがいも、ニンジン、タマネギ、ピーマンなどの野菜も切りましょう。
2. 大きめの鍋に油を熱し、肉を炒めます。肉に火が通って軽く焼き色がついたら、野菜を加えて炒めます。
3. 野菜がしんなりしてきたら、カレールーを加えます。一般的に市販のカレールーを使いますが、自家製のルーを使うこともできます。
4. カレールーを加えたら、水や出汁を少しずつ加えながら、ルーが溶けるまでかき混ぜます。カレーソースがとろみがつくまで、弱火で煮込みます。
5. カレーソースの味を調整します。塩やこしょうで味を整え、辛さが好みならチリパウダーや唐辛子を加えることもできます。
6. 完成したカレーソースをご飯の上にかけて、お好みでトッピングを加えましょう。お好みで、目玉焼きやフライドオニオン、ピクルスなどを添えることもできます。
以上が基本的なカレーライスの作り方です。好みに合わせてカスタマイズしてみてください。
モデルをgpt-4-1106-preview
にして同じ質問をしてみます。
カレーライスを作る基本的な方法をご紹介します。以下は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モードはモデルが限定されているので、モデルの値によってオブジェクトを隠すようにすると良いでしょう。
スクリプト修正
-
response_format: { type: "json_object" }
の作成 - JSONの文言が入っているかvalidation
を行なっています。
これをCompletionのスクリプトに組み込みます。
リクエストの前にresponse_format
を$body
に差し込みます。
これでJSONモードが使えるようになりました。
遊ぶ
まだカレーが食べたいので、カレーの作り方をJSONモードをOnにして聞いてみました。
"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を指定
- 指定しないと回答が途切れる。
{
"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
}
実装
ポイントをふまえて、実装していきます。画像はURLで渡す形式とします。
フィールド追加
image_url
を追加します。こちらもモデルの値に応じてオブジェクトを隠すと良いでしょう。
スクリプト修正
createUserMessage($message)
Vison APIではペイロードの形が異なるので調整します。
変数を設定
の中身を示します。
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
は、モデルを決め打ちで設定しているだけ。なくても構いません。
/chat/completitions
$body
を作った後にimage_url
が入っている場合は、max_token
に設定します。
ベタ打ちで800にしましたが、config
やフィールドで管理するのが良さそうです。
遊ぶ
検証画像として、カレーの画像を使います。
豚バラで作るコクうまポークカレー
https://housefoods.jp/recipe/rcp_00025463.html
こちらの画像のURLをフィールドにセットしてスクリプトを実行してみます。
こちらが回答。福神漬け..?
この画像は、具だくさんのおいしそうなカレーライスを写したものです。日本の家庭料理としてポピュラーな一皿で、温かいご飯の上に濃厚でスパイシーなカレーソースがたっぷりかかっています。カレーには肉や野菜がゴロゴロ入っており、色とりどりの具材が食欲をそそります。トッピングや隣に添えられたピクルスや福神漬けなどがあり、これらのアクセントがカレーの風味を引き立てていることでしょう。
Wikipediaから、けっこう有名な部類のホモ・サピエンスの画像を試してみます。
https://upload.wikimedia.org/wikipedia/commons/archive/6/6a/20200513060036%21Johann_Sebastian_Bach.jpg
回答はこちら。
この画像は、バロック時代の偉大な作曲家、ヨハン・セバスティアン・バッハの肖像画です。高解像度の写真で、その細部に至るまで精巧に描かれています。バッハは貴族的な姿で描かれており、豊かな装飾が施された服を着ています。彼の表情は深い思索にふけっているようで、彼の音楽の複雑さと緻密さを反映しているかのようです。
洒落た一言を追加してくれました。バッハの名前はURLの文字列でバレているかもしれません。
弊協会(ピティナ)のキャラクター「ぴてぃにゃん」だとどうなるでしょうか。4
https://corporate.piano.or.jp/ptnyan/index.html
画像には、ピアノをモチーフにしたキャラクターが描かれています。背景は主にピンク色を基調としており、キャラクターは白と黒の猫で、ピアノの鍵盤をイメージしたデザインの服を着ています。表情は優しく愛らしく、音符やピアノにちなんだ小物も描かれていることから、音楽やピアノに関連するキャラクターであることが想像されます。商業的な使用を想定したグッズやプロモーション素材などに使用されるかもしれません。また、このキャラクターが何を象徴しているか、またはどのようなストーリーがあるのかは画像からは明確ではありませんが、ポップでキュートな雰囲気が感じられます。
まずますです。まさにいまプロモーション素材などに使用され
た、ぴてぃにゃんであります。
...と、これらはわりとうまくいったケースをチェリー・ピッキングしています。URLをヒントにしている雰囲気も感じます。
元の画像の画質が良くない場合など、ファンタジーあふれる頓珍漢な回答が来るケースも多かったです。
4. DALL-Eの画像生成で遊ぶ
最後に画像生成で遊びます。例によってポイントのおさらいから。
ポイント
- エンドポイントは
/images/generations
- Chapter 1で設定済み。
- モデルは
dall-e-3
/dall-e-2
- OpenAI側でリクエスト後にシステムプロンプトが追加される。
- サンプルのリクエストは下記。
{
"model": "dall-e-3",
"prompt": "a white siamese cat",
"n": 1,
"size": "1024x1024"
}
https://platform.openai.com/docs/guides/images/introduction?context=node
実装
レイアウト修正
/images/generationsスクリプトの作成
画像生成といっても渡すものは多くなく、プロンプトも単一の"prompt"
のみなため、シンプルに実装できます。
$body
部分は下記の通り設定しています。
JSONSetElement ( "" ;
[ "model" ; OpenAICaller::model ; JSONString ];
[ "prompt" ; $prompt ; JSONString ]
)
以下がポイントです。
- OpenAIからのレスポンスの型がcompletionと異なる。
data[0].url
で画像のURLを取得。 - 画像のURLに対し、
URLから挿入
を行い、オブジェクトフィールドに保管
遊ぶ
結果がこちらです。あんまり美味しそうではない...?カレー、箸で食うん?
responseを示します。revised_prompt
の通り、プロンプトは変換されてから画像になります。
{
"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..."
}
]
}
これはおいしそうですね。
おわりに
以上、FileMakerでOpenAI APIを叩いて遊んでみました。
中間APIを立ててそこでリクエスト組み立て・レスポンスparseしたほうが楽では...と思わないこともないですが、FileMaker16でJSON関数が追加されたので、リクエストを送るのはそれほど困難ではなかったです。
オブジェクトフィールドに生成画像を突っ込めたり、UIが簡単に作れるのはFileMakerの魅力だなと感じました。
カレー食べたい。5
-
APIを「叩く」という表現、ちょっと暴力的ですね。本記事では肩をトントンとするように、APIは優しく叩くことを心がけています。 ↩
-
argにgetを取っているのは、グローバル引数のgetterとsetterを引数で切り替えている癖です。今回はとくに意味はありません。 ↩
-
余談ですが、壁紙かわいいのでダウンロードしてください -> https://corporate.piano.or.jp/ptnyan/index.html ↩
-
筆者は翌日カレーを食べた。 ↩