2024/07/26

作ってみよう:#7)お小遣い帳 - 現在位置から近隣の情報を取得 ②

前回は GPS から現在位置を取得する作業を行いました。今回はその座標を使って近隣の情報を GoogleMAP から取得します。

 

ライブラリの準備

GoogleMAP から情報を取得するプログラムは、別の連載『クラス化に挑戦』の第 6 回 ~ 第 13 回で作成した lsGoogleMAP スクリプトライブラリです。このライブラリを「お小遣い帳」DB にコピペします。

※ この記事を書いていて『#7)Google マップ - Place API の結果をクラス化 ②』 のプログラム内にバグを見つけました(リンク先の記事は修正済みです)。ご注意ください。


エージェントの作成

現在位置から近隣を検索する機能を作成します。他のフォームからの利用を考えてエージェントとして作成します。名称は「NearBySearch_Google」、「エージェントリストの選択」で対象は「なし」を指定します。

続いて、ライブラリを組み込み GoogleMAP に接続する準備をします。


最寄りの場所情報の取得

まずはライブラリ組み込みの確認を行います。とりあえず、現在位置から一番近い場所情報を GoogleMAP から取得します。これがうまく動作すれば組み込み成功ということですね。

Sub Initialize
   Dim nuiw As New NotesUIWorkspace
   Dim nuid As NotesUIDocument
   Dim nd As NotesDocument
   Dim oLoc As Location
   Dim oSch As NearBySearch
   Dim oPlace As Place

   Set nuid = nuiw.CurrentDocument
   Set nd = nuid.Document

   Set oLoc = New Location(nd.Sch_Latitude(0), nd.Sch_Longitude(0))
   Set oSch = New NearBySearch(oLoc)

   Call oSch.Search()

    MsgBox CStr(oSch.Count) & "件、見つかりました。"
   If oSch.Count > 0 Then
      Set oPlace = oSch.GetPlace_Nth(1)
      nd.Vendor = oPlace.PlaceName
   End If
End Sub

現在の文書から現在位置を読み込み座標(Location インスタンス)を作成します。その座標をもとに NearBySearch を行い、一番近い場所の名称を Vendor フィールドにセットしています。

正しく動作すると、最も近隣の情報が表示されます。


次回の予告

最寄りの情報一つでは必要な情報が得られないですね。次回は、検索結果の中から選択する機能を作成します。


Notes クライアントでテストする場合

テストするたびに Nomad を使用して GPS 座標の取得が手間な場合は、座標のフィールドに初期値をセットするようしてください。前回作成した GPS の現在位置取得はフィールドが空の場合のみ実行しますので、初期値を与えると停止します。


前回 作ってみよう


2024/07/25

作ってみよう:#6)お小遣い帳 - 現在位置から近隣の情報を取得 ①

アプリケーション作ることを主体にした企画、現在作成中の『お小遣い帳』をいよいよ Nomad アプリらしい作りこみを行います。GPS から現在位置を取得して、GoogleMAP API と連携し、近隣の情報を表示する機能を追加します。

今回の作業では、他の記事で作成した機能を流用して、組み上げます。詳細が知りたいときはそちらの記事もご確認ください。

先月リリースされたNomad iOS 1.0.40 では iOS のバージョンによっては WebAPI が動かない問題がありました。iOS の方は先日リリースされた 1.0.41 にアップデートの上お試しください。


作成する機能

まずは、スマホの GPS から現在位置の取得です。フォームを開くと下図の通り、現在位置の座標をフィールドに記録します。


ライブラリの準備

使用するのは、『HCL Nomad で GPS 座標の取得』で作成した現在位置を取得する関数 GetGPSPos_Cur 関数です。この記事では、スクリプトライブラリ lsGPS にまとめていましたので、この DB にコピペするなどして作成します。


フィールドの作成

続いて、モバイル用フォーム『m.支出』を開き、座標を表示するフィールド を作成します。

フィールド名 種類
Sch_Latitude 数値 作成時の計算結果 ""
Sch_Longitude


現在位置の取得

まずは、先ほど作成したスクリプトライブラリをフォームに組み込みます。(Globals) の (Options) でライブラリの使用を宣言します。

続いて、フォームを開いたときに、現在位置を自動で取得させます。フォームの PostOpen イベントでライブラリの関数をコールして現在位置を取得、その座標をフィールドにセットします。

記述するコードは下記の通りです。新規作成時のみ取得するよう、編集モードで、座標が空の場合のみ実行するようにしています。

Sub Postopen(Source As Notesuidocument)
   Dim nd As NotesDocument
   Dim dLat As Double
   Dim dLng As Double

   Set nd = Source.Document

   If Source.EditMode = True Then
      If nd.Sch_Latitude(0) = "" Then

         If GetGPSPos_Cur(dLat, dLng) Then
            nd.Sch_Latitude = dLat
            nd.Sch_Longitude = dLng
         End If
      End If
   End If
End Sub


ここまでできあがったら、Nomad でプリビューして現在位置が取得できているのか確認しましょう。

前回 作ってみよう


2024/07/22

ファイル一覧の取得と存在チェック

先日、ファイル操作を行うプログラムを作成している際にヘルプのお世話になるタイミングがありました。備忘録としてまとめておきたいと思います。また、その際に注意点も発見したので整理しておきます。


ファイルの存在チェック

ファイルを作成する処理を書いていると保存前に同名のファイルの存在チェックをしたい場合がありますよね。まずは、その方法です。短いコードですが関数化しました。

   Function xIsExists(ByVal vsFilePath As String) As Boolean
      Dim s As String
      s = Dir$(vsFilePath)
      xIsExists = (s <> "")
   End Function

引数に調べたいファイル名(フルパス)で指定します。存在する場合 True、存在しない場合 False を返します。

仕組みは単純で Dir ステートメントを使用しているだけです。DOS コマンドの Dir コマンドに似ていて引数に合致するファイルがあれば、そのファイル名を返します。フルパスを引数で指定しても、戻り値はファイル名だけとなります。何らかの戻り値があればファイルが存在することになります。

Dir ステートメントの戻り値は Variant です。Dir$ とすると戻り値を文字列に固定できます。今回は文字列の方がよいので Dir$ を使用しています。


補足ですが、以下の部分を疑問に思われる方がいるかもしれません。

      xIsExists = (s <> "")

If 文で見かける比較があります。<> などの比較演算子は比較した結果を True か False で返します。If 文はこの結果を使って分岐を実現していますが、ここでは True か False を返す機能をそのまま利用しています。ちなみに、( ) でくくらないと文法エラーとなりますので注意してください。


ファイルの一覧の取得

続いてはフォルダ内にどのようなファイルがあるか調査する方法です。

例えば、C:\ に 1.txt、2.txt、3.txt を 3 つのテキストファイルがあったとします。以下のプログラムを実行すると、1.txt が変数 s にセットされます。

   s = Dir$("c:\*.txt")

この状態で Dir を引数なしで実行すると 2.txt が返ってきます。条件を省略すると次のファイルを返してくれるという仕組みになっています。

   s = Dir$()

この機能を利用して、戻り値が空になるまで繰り返すとフォルダ内のファイルの一覧が取得できます。例えば、以下のプログラムはノーツのデータディレクトリ内の domino\icons フォルダの gif ファイルを順に取得する処理です。

   Dim s As String
   Dim v As Variant

   '調査フォルダ取得
   s = ns.GetEnvironmentString("Directory", True)   'データディレクトリ
   v = Split(s, "\")
   ReDim Preserve v(UBound(v)+2)
   v(UBound(v)-1) = "domino"
   v(UBound(v)) = "icons"
   s = Join(v, "\") & "\"

   'gifファイルを順に取得
   Dim sGif As String
   sGif = Dir$(s & "*.gif", 0)

   While Not(sGif = "")
           ・・・gifファイルの処理を記述・・・

      '次のファイル
      sGif = Dir$()
   Wend


問題発生 !?

この 2 つの機能を利用して使用して、アプリを作成していたのですが、ある時突然アプリが正常に動作しなくなりました。1 つ目のファイルだけ取得でき、2 つ目以降のファイルが取得できなくなるという症状でした。

この現象の発生は、gif ファイルの処理を改造したタイミングで、ファイルを保存する際に存在チェックを行った(xIsExists 関数をコールした)直後からでした。

そうなんです。Dir ステートメントで次のファイルを取得する機能は、最後に実行した Dir の条件を使用してしまいます。xIsExists 関数をコールしたことにより Dir の条件が更新され、While ループに戻り、sGif = Dir$() を実行したときには、もう gif のことは忘れていたということです。なかなか潔いステートメントですね。


まとめ

今回、この問題は過去の作成したライブラリの内部で Dir ステートメントを使用していたため発生しました。ずいぶん前のコーディングだったので、発見が遅れました。

この問題って、グローバル変数を使った時に発生する弊害に近しいですよね。

ライブラリ開発においては、グローパル変数を使用しないなど独立性が下がらないよう意識しています。ライブラリ内でこのステートメントを使うと外部のプログラムに影響を与え、同様の課題をはらみます。ライブラリなど再利用する部品で、使わないほうがよいですね。

2024/07/19

つないでみよう:#18)GPT4o で画像認識 - API のコールとテスト

前回で送信する JSON の準備ができたので、いよいよそれを使って API をコールする部分を作成し、実際に実行してみます。


API のコール

最後の関数となるのは、エージェントからコールされる API に接続し、その結果を返す関数 xAskGPT です。

処理の流れは前回 GPT 連携事例として紹介した『#2)ChatGPT API の利用』のサンプルプログラムとほぼ同等です。POST する JSON は引数、Bearer は定数で宣言していますので、プログラムがすっきりしていますね。また、API からのレスポンスをこの関数の戻り値にしている点も違います。

Function xAskGPT(ByVal vsJSON_Post As String) As String
   Dim sURL As String
   Dim http As NotesHTTPRequest
   Dim jnav As NotesJSONNavigator
   Dim ja As NotesJSONArray
   Dim jobj As NotesJSONObject

   'HTTP リクエストの準備
   Set http = xns.CreateHTTPRequest()

   'HTTP ヘッダーの設定
   Call http.SetHeaderField("Content-Type", "application/json")
   Call http.SetHeaderField("Authorization", "Bearer " & xcsBearer)

   'API コール
   http.PreferJSONNavigator = True
   sURL = "https://api.openai.com/v1/chat/completions"      'エンドポイント
   Set jnav = http.Post(sURL, vsJSON_Post)

   'Responce をセット
   xAskGPT = jnav.Stringify
End Function


テスト

エージェントが完成したのでテストします。

フォームから文書を作成し「AI の役割」と「リクエスト」「送信する画像」を添付して保存します。その文書に対してエージェントを実行すると AI からの返答が「受信した JSON」に表示されます。

受信した JSON は choices ノードの role が assistant となっている message が AI からの返答となり、content ノードがその内容です(詳しくは 第2回 を参照)。


画像を銅像の写真、質問を『この画像は、桂浜の坂本龍馬像です。解説をしてください。』で固定して、役割を変えて送信し、返答を比較してみました。

役割: あなたは大阪弁をしゃべるツアーガイドです。

おお、ええ写真やないかい!これは高知県桂浜に立つ坂本龍馬の銅像や。坂本龍馬は幕末の志士として有名で、日本の近代化に大きく貢献した人物や。銅像は彼の勇ましい姿を表現しとるなあ。
この銅像は1950年に建立されたんやけど、龍馬の地元である高知のシンボルとして親しまれとるんやで。龍馬は維新の立役者で、薩摩藩と長州藩の同盟(薩長同盟)を結びつけたことで有名なんよ。
どうや、桂浜で潮風感じながら龍馬の像見てたら、まるで幕末の風が吹いてるような気がしてくるやろ?
桂浜自体もすごく美しいスポットで、観光にはもってこいやで。よし、桂浜でリラックスして、龍馬さんと一緒に幕末のロマンを感じてみてな!いやぁ、龍馬さんもビーチ好きやったんかも知らんで(笑)

役割:あなたは高知弁を話すツアーガイドです。

あ〜、よっしゃ。この像は、桂浜(かつらはま)っちゅう高知県の有名な場所に立っちゅう坂本龍馬(さかもと りょうま)の像ながよ。龍馬は幕末(ばくまつ)の志士(しし)で、日本の近代化に大きく貢献した偉人じゃ。
この銅像は1969年(昭和44年)に建立されたもんやがよ。龍馬がご存知のとおり、日本の明治維新(めいじいしん)に大きな役割を果たし、特に土佐(とさ)藩出身の彼は、薩摩(さつま)藩と長州(ちょうしゅう)藩の同盟(薩長同盟:さっちょうどうめい)を仲介(ちゅうかい)したことで有名ぜよ。
桂浜は太平洋(たいへいよう)に面しちょって、美しい砂浜と松林(まつばやし)が広がっちゅう場所ながよ。高知を訪れる観光客にはこの桂浜で龍馬の像を見学することが一大イベントやき、ぜひ一度訪れてみてください!
もちろん、高知にはまだまだ色んな見どころが山盛りやき、ゆっくり楽しんでいってくださいね。

AI は方言も操れるんですね!
方言ネイティブな方には違和感のある表現が見受けられますが、面白いですね。


ところで、この結果で気が付いた点があります。

1つは、毎回答える内容が違うこと。もう一つは高知弁の場合は(読みかな)が多くついている点です。AI をシステムで活用するには、このようなブレは少ない方が望まし場合が多いですよね。役割の設定と質問の仕方で条件をしっかり与えると回答の自由度が狭まり、安定してきます。このあたりが AI 活用のポイントになりそうです。


前回 連載:つないでみよう


2024/07/18

つないでみよう:#17)GPT4o で画像認識 - 送信する JSON と作成 ②

前回 GPT4o に送信する JSON のフォーマットについて全体像とそれを作成する関数を紹介しました。今回はその中に画像ファイルを含める方法についてまとめします。


画像の送信方法

前回紹介した JSON フォーマットで、画像の送信は次のように表していました。

               "image_url": {
                  "url": "data:image/jpeg;base64,{・・・(送信する画像)・・・}"
               }

この部分は仕様を詳しく調査できていないのですが、この指定でうまく返信を得ることができました。image_url には url というサブノードを指定する形式となっています。

そして、url の値は "data:image/jpeg;base64," という固定の文字列に Base64 でエンコードした画像データを { } で括って与えます。

ちなみに "image/jpeg" と記述されていますが、送信する画像は jpeg でなくても動作しました。例えば、PNG 画像を Base64 でエンコードして与えてみましたが、認識してくれました。現時点では柔軟に対応してくれる仕様だと都合よく解釈し、気にせず進めます。


添付ファイルを Base64 に変換

まずは、メインルーチンからコールされる関数 xAttachmentToBase64 です。処理する文書とファイルが添付されているフィールド名が引数です。

Function xAttachmentToBase64(vnd As NotesDocument, _
                                              ByVal vsFldName As String) As String
   'Base64に変換
   Dim sBase64 As String
   Dim sName As String
   Dim nst As NotesStream

   'ファイルをダウンロードしてファイル名を取得
   sName = xSaveAttachment(vnd, vsFldName)

   'ファイルをBase64に変換
   Set nst = xns.CreateStream()
   Call nst.Open(sName)
   xAttachmentToBase64 = StreamToBase64(nst)
End Function

この関数は、添付ファイルを Base64 に変換する処理のメインルーチンにあたり、ファイルをダウンロードする関数とファイルを変換する関数の2つのサブ関数をコールしています。


添付ファイルのダウンロード

サブ関数の1つ目、添付ファイルを指定したフィールドからダウンロードする関数です。文書とフィールド名が引数です。

この関数はフィールド内の最初の添付ファイルを Windows テンポラリフォルダに保存します。そのファイル名(フルパス)を戻り値で返します。

Function xSaveAttachment(vnd As NotesDocument, ByVal vsFldName As String) As String
   Dim ni As NotesItem
   Dim nrti As NotesRichTextItem
   Dim nemb As NotesEmbeddedObject
   Dim sFol As String
   Dim sName As String
   Dim vTmp As Variant
   Dim i As Integer
   Dim v As Variant

   'Windows テンポラリフォルダを取得
   sFol = GetWinTmpPath()

   'フィールドを取得
   Set ni = vnd.GetFirstItem(vsFldName)
   If ni.Type = RICHTEXT Then
      'リッチテキストなら添付ファイルをチェック
      Set nrti = ni
      vTmp = nrti.EmbeddedObjects
      If Not (IsEmpty(vTmp)) Then
         For i = 0 To UBound(vTmp)
            '埋め込みオブジェクトを順に取得
            Set nemb = vTmp(i)
            If nemb.Type = EMBED_ATTACHMENT Then
               '画像ファイルであることを前提
               sName = nemb.Source
               v = Split(sName, ".")
               'ファイル名を決定
               sName = sFol & "denaoshi" & Format(Now, "yyyymmddhhnnss") & v(UBound(v))
               '添付ファイルを保存
               Call nemb.ExtractFile(sName)
               Exit For
            End If
         Next
      End If
   End If

   xSaveAttachment = sName
End Function

添付ファイルのダウンロードについては、リッチテキストに関する連載の『#15)添付ファイルのダウンロード』でも紹介しています。必要に応じて参照ください。


ファイルを Base64 に変換

Base64 にエンコードする関数は、連載『DXL Step-by-Step』の『#10)イメージリソースの新規作成』で紹介しています。この StreamToBase64 関数をそのまま流用します。今回必要なのはこの関数だけですので、エージェントにコピペで貼り付けます(コードは上記リンク先から取得ください)。

StreamToBase64 関数は、引数が NotesStream で戻り値が Base64 でエンコードされた文字列です。そこで、呼び出し元の xAttachmentToBase64 関数内では、ダウンロードしたファイルをストリームとして開き、StreamToBase64 関数に渡しています。

   'ファイルをダウンロードしてファイル名を取得
   sName = xSaveAttachment(vnd, vsFldName)

   'ファイルをBase64に変換
   Set nst = xns.CreateStream()
   Call nst.Open(sName)

   xAttachmentToBase64 = StreamToBase64(nst)


次回の予告

これで JSON の作成は完了です。次回は API をコールする部分を作成します。


前回 連載:つないでみよう 次回


2024/07/17

つないでみよう:#16)GPT4o で画像認識 - 送信する JSON と作成 ①

今回は GPT4o に送信する JSON を作成します。フォーマットは GPT3.5 の場合とほぼ同様ですが、画像が含まれる点が大きく変わります。


JSON のフォーマット

GPT4o に送信するフォーマットの全体構成は、次の通りです。

{
   "model": "gpt-4o-2024-05-13",
   "messages": [
      {
         "role": "system",
         "content": "・・・(AI の役割)・・・"
      }
,
      {
         "role": "user",
         "content": [

            {
               "type": "text",
               "text": "・・・(リクエスト)・・・"
            }
,
            {
               "type": "image_url",
               "image_url": {
                  "url": "data:image/jpeg;base64,{・・・(送信する画像)・・・}"
               }
            }

        ]  
      }
   ]
}

まず、role が system となっている部分です(青字)。この指定に続く content ノードで AI の役割を指示できます。

続いて、こちらからのリクエストにあたる role が user の部分(赤字)です。今回は、質問内容(テキスト)と認識させる画像の2つを同時に送信します。そのため、content ノードは [ ] を使い配列となっています。

配列の中身は、type ノードで送信するデータの種類、次のノードでデータを指定します。type の指定は text で文字列(紫の文字)、image_url で画像(赤紫の文字)となります。


送信する JSON の作成

上記フォーマットに従い JSON を作成する関数を作ります。前回のメインルーチンからコールされる xGetJSON 関数で、引数の vnd は質問や画像を入力した文書です。

Function xGetJSON(vnd As NotesDocument) As String
   Dim jnav As NotesJSONNavigator
   Dim jaMsg As NotesJSONArray
   Dim joMsg As NotesJSONObject
   Dim jaCnt As NotesJSONArray
   Dim joCnt As NotesJSONObject
   Dim jo As NotesJSONObject
   Dim s As String
   Dim sBase64 As String

   'RequestBody(JSON) の準備
   Set jnav = xns.CreateJSONNavigator("")

   'Lv1. model
   Call jnav.AppendElement("gpt-4o-2024-05-13","model")

   'Lv1. message
   Set jaMsg = jnav.AppendArray("messages")

   'Lv2. system role
   Set joMsg = jaMsg.AppendObject()
   Call joMsg.AppendElement("system", "role")
   Call joMsg.AppendElement(vnd.System_Content(0), "content")


   'Lv2. user role
   Set joCnt = jaMsg.AppendObject()
   Call joCnt.AppendElement("user", "role")

   Set jaCnt = joCnt.AppendArray("content")


   'Lv3. Request
   Set joCnt = jaCnt.AppendObject()
   Call joCnt.AppendElement("text", "type")
   Call joCnt.AppendElement(vnd.User_Content(0), "text")


   'Lv3. Request(Picture)
   Set joCnt = jaCnt.AppendObject()
   Call joCnt.AppendElement("image_url", "type")

   sBase64 = xAttachmentToBase64(vnd, "Body")
   s = "data:image/jpeg;base64,{" & sBase64 & "}"
   Set jo = joCnt.Appendobject("image_url")
   Call jo.AppendElement(s, "url")


   xGetJSON = jnav.Stringify
End Function


次回の予告

上記関数内の xAttachmentToBase64 は添付された画像ファイルを Base64 に変換し、文字列で返す関数です。次回はこの関数を紹介します。


前回 連載:つないでみよう 次回


2024/07/16

つないでみよう:#15)GPT4o で画像認識 - エージェントの作成

前回からスタートした GPT4o の画像認識を API から実行するサンプル作り。今回は API をコールする部分を作成します。


エージェントの作成

サンプルアプリですので、ビューで選択した文書に対して実行する仕様とします。

まずは、LotusScript のエージェントを作成し、選択文書に対して実行するように設定します。メインルーチンは次の通りです。

Option Declare
Use "lsWindows" 'Windows のテンポラリフォルダの取得

Private xns As NotesSession
Private Const xcsBearer = "xxxxx" 'APIキーをここにセット

Sub Initialize
   Dim ndb As NotesDatabase
   Dim ndc As NotesDocumentCollection
   Dim nd As NotesDocument
   Dim sBase64 As String
   Dim sJSON_Post As String
   Dim sJSON_Responce As String

   Set xns = New NotesSession

   'ビューの選択文書を1つ取得
   Set ndb = xns.CurrentDatabase
   Set ndc = ndb.UnprocessedDocuments
   If ndc.Count = 0 Then
      MsgBox "文書を選択してください。", 16, ndb.Title
      Exit Sub
   End If
   Set nd = ndc.GetFirstDocument()
   
   '① 送信する JSON を作成
   sJSON_Post = xGetJSON(nd)
   'Call xSetRT(nd, "JSON_Send", sJSON_Post) '必要な時だけ有効化

   '② API をコールして結果を取得
   sJSON_Responce = xAskGPT(sJSON_Post)
   Call xSetRT(nd, "JSON_Responce", sJSON_Responce) '結果を文書に記録

   Call nd.Save(True, False)
End Sub

選択文書(の1文書目)を取得した後、

① リクエスト内容を読み取り API に POST する JSON を作成
② API をコールして結果の JSON を取得

する関数をコールしています(処理の詳細は後述)。


結果の JSON は文書内のリッチテキストに保存しています(xSetRT 関数)。送信する JSON は画像が含まれますので、生成される JSON は非常に大きくなります。文書を開くのが遅くなりますので、中身を確認したい時だけ有効化にすることをお薦めします。

Function xSetRT(vnd As NotesDocument, ByVal vsFld As String, ByVal vsVal As String)
   Dim nrti As NotesRichTextItem

   Call vnd.RemoveItem(vsFld)
   Set nrti = vnd.CreateRichTextItem(vsFld)
   Call nrti.AppendText(vsVal)
End Function


なお、利用している lsWindows ライブラリは、添付ファイルの一時保存用に、Windows のテンポラリフォルダを取得するために使用しています。このライブラリについては、以下の記事を参照してください。

Windows のテンポラリフォルダの取得


次回の予告

今回は、メインルーチンを準備しました、次回は POST する JSON を作成する部分を開発します。


前回 連載:つないでみよう 次回


2024/07/15

つないでみよう:#14)GPT4o で画像認識 - 準備作業

2024 年 5 月に OpenAI から新たしい AI モデル GPT4o が発表されました。

この連載『つないでみよう』の最初のネタが GPT3.5 でした。昨年 10 月末ごろでしたが、それから半年ちょっと。その間、GPT4、そして今回の GPT4o とバージョンアップしています。

早く記事にしないと新バージョンでなくなってしまいそうなので、急ぎます...


さて、その GPT4o にはさまざま特徴があるのですが、私が興味を持ったのは画像入力に対応したことです。これを利用すると、画像の説明を AI にさせることが可能になります。もちろん API からも利用可能です。今回はこれを題材に最新モデル GPT4o を利用してみましょう!


API の利用方法

OpenAI の API を利用するには事前登録が必要です。登録時に無料枠がありますが、その後は有償となります。

登録方法は 第 1 回 で紹介しています。バージョンアップで画面は多少変化していますが、必要な作業は同じようです。登録してAPIキーを準備しましょう。なお、API キーは共通です。同じキーで GPT3.5 も GPT4o も利用できます。過去のキーをお持ちの方はそのまま流用できます。

API をコールする URL にあたるエンドポイントのアドレスも同じです。

POST  https://api.openai.com/v1/chat/completions

GPT4o を使用するには POST する JSON でモデルを指定するだけです。

{
   "model": "gpt-4o-2024-05-13",
   "messages": [
         ・・・


作成するアプリのイメージ

今回作成するサンプルアプリの画面は次のような感じです。

質問を調整しながら何度も送信できるよう、リクエストを自由に入力できるようにします。また、今回は AI の役割を指示する機能を付けて、回答の精度向上を狙います。


フォームの作成

最初にフォームを作成します。重要なのは[リクエストの内容]の部分です。


タイトルと概要はビューに表示するための項目となっています。また、紫色の文字は非表示です(1行目はフォーム名を転記)。

また、フォーム下部に API に送信した JSON と受信した JSON のフィールドを作成します。デバッグ用のフィールドなので、不要になったら削除しましょう。


フォームができたら、このフォームの文書を表示するビューを作っておきましょう。


次回の予告

今回は、GPT4o に接続する準備を行いました。次回からはこれらを利用して GPT4o に接続する部分を作成します。


前回 連載:つないでみよう 前回


2024/07/07

クラス化に挑戦: #13)クラス機能の追加

前回までの Google マップ - Place API を利用する機能をクラス化しました。スマホ用の Nomad と組み合わせると、現在地を中心に近隣を検索できるようになります(HCL Nomad で GPS 座標の取得)。最寄りのお店や施設を検索するなどさまざまな利用方法が考えられますね。

このような機能を実現すると、情報として現在位置からの距離が欲しくなります。そこで、地点情報を表す Location クラスに距離を計算する機能を追加します。


作成するメソッド

作成するメソッドの使い方は次の通りとします。

   dDistance = oLoc1.CalcDistance(oLoc2)

oLoc1、oLoc2 は座標を表す Location クラスのインスタンスです。距離を計算するメソッド CalcDistance を実行すると 2 点間の距離を計算して返す機能となります。


距離の計算

緯度経度で表される座標は球面を前提としています。そのため距離に変換するためには、地球の大きさなどを利用して計算する必要があります。

先輩ブロガーの中野さんが下記の記事で関数を公開されています。この記事で紹介されている distance という関数を流用させていただきます。

Nomad で GPS を使う

LotusScript で記述されているので、そのまま使えます。ありがたいですね!


クラスの修正と確認

コード量の多いクラスではありませんので、クラス全体のコードを掲載します。

赤字の部分がオリジナルの追加コードです。青字の部分がブログから拝借した関数なのですが、クラス内部の関数として使用していますので private 宣言だけ追加しています。

Public Class Location
   Private zdLatitude As Double '緯度
   Private zdLongitude As Double '経度

   Public Sub New(ByVal vdLatitude As Double, ByVal vdLongitude As Double)
      zdLatitude = vdLatitude
      zdLongitude = vdLongitude
   End Sub

   Public Property Get Latitude As Double
      Latitude = zdLatitude
   End Property

   Public Property Get Longitude As Double
      Longitude = zdLongitude
   End Property


   Public Function CalcDistance(voDestination As Location) As Double
      CalcDistance = distance(zdLatitude, zdLongitude, voDestination.Latitude, voDestination.Longitude)
   End Function

   Private
Function distance( lat1 As Double, lng1 As Double, lat2 As Double, lng2 As Double ) As Double
      Dim radLat1 As Double, radLng1 As Double
      Dim radLat2 As Double, radLng2 As Double
      Dim aveLat As Double, aveLng As Double
      Dim c As Double
      Const r = 6378137.0
'赤道半径

      '円弧の長さを扱うため、角度(緯度経度)をラジアンへ変換
      c = 180 / PI
      radLat1 = lat1 / c
      radLng1 = lng1 / c
      radLat2 = lat2 / c
      radLng2 = lng2 / c

      aveLat = ( radLat1 - radLat2 ) / 2
      aveLng = ( radLng1 - radLng2 ) / 2

      distance = r * 2 * ASin( Sqr( Sin( aveLat ) ^ 2 + Cos( radLat1 ) * Cos( radLat2 ) * Sin( aveLng ) ^ 2 ) )
   End Function
End Class


動作検証として簡単単なエージェントを作成します。

Option Declare
Use "lsGoogleMAP"

Sub Initialize
   Dim oLoc1 As Location
   Dim oLoc2 As Location

   Set oLoc1 = New Location(34.68374, 135.49698)
   Set oLoc2 = New Location(34.68354, 135.49616)

    MsgBox CStr(Format(oLoc1.CalcDistance(oLoc2), "0.00")) & "m"
End Sub

適当な 2 つの座標 oLoc1 と oLoc2 を用意し、CalcDistance メソッドを実行します。これで 2 地点間の距離が求められますので、それをメッセージボックスで表示しています。


まとめ

クラスに機能(メソッド)を追加する方法を紹介しました。以前作成したクラスに具体的な機能を追加したので、イメージしやすかったと思います。この作業を材料に、クラス化の効果について整理します。

例えば Location クラスとして提供した機能を通常の関数群として開発したとします。追加機能である CalcDistance 関数は、スクリプトライブラリ内に Public な関数として定義が必要です。スクリプトライブラリを利用するプログラムからアクセスできる必要があるからですね。

Public な関数はプログラム全体で一意である必要があります。もし、このスクリプトライブラリを利用するエージェントやフォームで別の CalcDistance 関数が存在すると、エラーとなります。このライブラリを利用するには、ライブラリ外で名称を変える調整(修正)が発生することになります。

クラス化した場合は oLoc1.CalcDistance(oLoc2) のような記述になります。通常の関数とはスコープが違うので、文法エラーにはなりません。

このように、クラス化しておくと既存プログラムに対する影響を低減させる効果があります。また、この二次的な効果としてメソッドやプロパティ名をシンプルにできる効果があります。クラス内で名称が一意であればよく、別のクラスで同名があっても別扱いになるからですね。

クラス化したほうがより部品化が進めやすくなるということですね。


前回 クラス化に挑戦


2024/06/30

DXL のヘルプ はじめました!

今年は、DXL の理解を進めることをメインの目標にしてノーツに取り組んでいます。このブログでも、昨年掲載した『DXL ことはじめ』にはじまり、『DXL Step-by-Step』の連載は現在も継続中です。

DXL はドキュメントなどの情報が少ないため、連載の材料収集はトライ&エラーで収集しています。わかったことは都度記事にまとめていますが、一覧性がなく、必要なものを探し出しにくい状態になっています。

そこで、DXL のノードや属性をまとめたサイトを作ることにしました。


題して『出直し!! ヘルプ DXL ノード編』です!


出直し!! ヘルプ DXL ノード編

今年1月に公開した @関数編 の DXL 版というところですね。右の『出直し!! ヘルプ』メニューからアクセスしてください。

左の一覧はノード名順のインデックス、右側は機能別にノードをまとめて表示しています。


このヘルプでは、ノードを主軸に、

  • 上位のノード(Contained by)
  • 下位のノード(Contains)
  • 属性(Attributes)
  • 当ブログ内の関連記事

をまとめています。


乞うご期待!

もう少しまとまってからの公開を考えていたのですが、まとめる情報量がお余りに多くいつまでたってもゴールが見えないこと、すでに多くの情報が集まったことから公開することとしました。

書きかけの項目や不足しているノードが多数あり、まだまだ未完成な状態です。今後の調査で分かったことも日々追記して、少しずつ充実させていきます。発展途上であることをあらかじめご了承ください。


DXL を使えば、LotusScript の限界を超えることができます。興味のある方は、冒頭に紹介した連載とこの『出直し!! ヘルプ DXL ノード編』でチャレンジください。


すでに DXL で開発している超レアリティが高い方にも参考になるようまとめていく予定です。参考になれば幸いです!