エンドポイントURLの構築

EXCEL

VBA/PowerShellからAzure OpenAI APIを呼び出すディープダイブ

導入(問題設定)

既存の業務システムがVBAやPowerShellで構築されている現場において、最新のAI技術、特にAzure OpenAI Serviceの力を取り入れたいというニーズは高まっています。しかし、その実現には障壁が少なくありません。外部ライブラリの導入が難しい、COMオブジェクトやPowerShellネイティブの機能だけでどこまでできるのか、といった疑問は尽きません。

本稿では、こうした現場の深い悩みに応えるべく、VBAおよびPowerShellからAzure OpenAI ServiceのChat Completions APIを直接、かつ極力外部ライブラリに依存せずに呼び出す方法を、その内部動作、境界条件、そして陥りやすい落とし穴まで含めてマニアックに解説します。単なるHowToに留まらず、最小実装から堅牢化までの道のりを示し、あなたのスクリプトを次世代のAI駆動型システムへと昇華させるための知識を提供します。

理論の要点

Azure OpenAI Serviceとの連携は、基本的にREST APIを介して行われます。クライアント(VBA/PowerShell)からHTTPリクエストを送信し、JSON形式で構成されたリクエストボディにプロンプトやパラメータを含めて送信。サービスから返されるJSONレスポンスを解析し、AIの生成結果を得る、という流れです。

認証とエンドポイント

Azure OpenAI ServiceのREST APIは、通常、APIキーベースの認証を採用します。このAPIキーは、HTTPヘッダーにapi-key: YOUR_API_KEYとして含める必要があります。 エンドポイントURLは以下の形式を取ります。 https://{your-resource-name}.openai.azure.com/openai/deployments/{your-deployment-id}/chat/completions?api-version=2023-05-15

  • {your-resource-name}: Azure OpenAI Serviceリソースの作成時に指定した名前。
  • {your-deployment-id}: デプロイしたモデル(例: gpt-35-turbo)に付けたデプロイID。
  • api-version: 使用するAPIのバージョン。公式ドキュメントで最新版を確認してください。

Chat Completions APIの主要パラメータ

Chat Completions APIは、POSTメソッドでJSON形式のリクエストボディを送信します。

パラメータ名 必須/任意 説明
messages 必須 配列 ロールとコンテンツを持つメッセージオブジェクトの配列。AIとの会話履歴を表現。
  role 必須 文字列 メッセージの送り手 (system, user, assistant)。
  content 必須 文字列 メッセージの本文。
temperature| 任意 数値 生成されるテキストの多様性(ランダム性)を制御。0.0(決定的)~2.0(創造的)。デフォルト1.0。
max_tokens 任意 整数 応答で生成されるトークンの最大数。入力と出力の合計はモデルのコンテキストウィンドウを超えられない。
top_p 任意 数値 nucleusサンプリング閾値。temperatureと組み合わせて多様性を制御。0.0~1.0。デフォルト1.0。
stop 任意 文字列/配列 AIが生成を停止するシーケンス。最大4つまで。
stream 任意 真偽値 応答をストリーミング形式で受け取るか。trueの場合、応答はServer-Sent Events形式となる。

API呼び出しのシーケンス

sequenceDiagram
    participant "Client as VBA/PowerShell Script"
    participant "Proxy as Corporate Proxy (Optional)"
    participant "AOAIS as Azure OpenAI Service"
    participant "AOAIModel as Azure OpenAI Model"

    Client ->> Proxy: HTTP POST Request (JSON Body, api-key Header)
    alt No Proxy
        Client ->> AOAIS: HTTP POST Request (JSON Body, api-key Header)
    end
    Proxy ->> AOAIS: Forward HTTP POST Request
    AOAIS ->> AOAIS: Authenticate API Key & Validate Deployment
    AOAIS ->> AOAIModel: Forward Chat Completions Request (Messages, Parameters)
    AOAIModel -->> AOAIS: Generate Response
    AOAIS -->> Proxy: HTTP 200 OK Response (JSON Body)
    Proxy -->> Client: Forward HTTP 200 OK Response
    Client ->> Client: Parse JSON Response & Extract AI Content

実装(最小→堅牢化)

共通の準備

Azure OpenAI Serviceのリソース作成とモデルのデプロイを済ませておいてください。 エンドポイントURLとAPIキーを控えておきます。 例: – リソース名: my-aoai-resource – デプロイID: gpt-35-turbo-deploy – APIキー: YOUR_SUPER_SECRET_API_KEY – APIバージョン: 2023-05-15

VBA実装

VBAではWinHttpRequest.5.1オブジェクトを使用してHTTPリクエストを送信します。JSONの生成と解析は、ネイティブ機能で文字列操作するか、軽量なクラスモジュールを利用する選択肢があります。本稿では「極力外部ライブラリなし」のため、文字列操作を基本とします。

最小実装 (VBA)

Excel VBAで簡単なチャット対話を行う例です。

Attribute VB_Name = "Module1"

' 64bit環境対応: VBA7以降のLongPtrはポインタサイズに合わせる
' WinHttpRequestオブジェクト自体はCOMなのでPtrSafeは直接関係ないが、
' API宣言が必要な他のCOMオブジェクトやDLL関数を使う際に重要
' LongPtrは64bit環境で8バイト、32bit環境で4バイトとなる
' LongLongは64bit整数型で、64bit環境で利用可能
' 今回のWinHttpRequestの使用では直接のPtrSafe/LongPtrは不要だが、
' 念のためアーキテクチャ差への注意喚起として記しておく。

Private Const AZURE_OPENAI_RESOURCE_NAME As String = "my-aoai-resource" ' あなたのリソース名
Private Const AZURE_OPENAI_DEPLOYMENT_ID As String = "gpt-35-turbo-deploy" ' あなたのデプロイID
Private Const AZURE_OPENAI_API_VERSION As String = "2023-05-15" ' 使用するAPIバージョン
Private Const AZURE_OPENAI_API_KEY As String = "YOUR_SUPER_SECRET_API_KEY" ' あなたのAPIキー

Sub CallAzureOpenAI_Minimal()
    Dim httpRequest As Object ' WinHttpRequestオブジェクト
    Dim strURL As String
    Dim strRequestJson As String
    Dim strResponseJson As String
    Dim queryPrompt As String

    queryPrompt = InputBox("AIに質問を入力してください:", "Azure OpenAI Chat", "日本の首都は?")
    If queryPrompt = "" Then Exit Sub

    ' エンドポイントURLの構築
    strURL = "https://" & AZURE_OPENAI_RESOURCE_NAME & ".openai.azure.com/" & _
             "openai/deployments/" & AZURE_OPENAI_DEPLOYMENT_ID & "/chat/completions" & _
             "?api-version=" & AZURE_OPENAI_API_VERSION

    ' リクエストボディのJSONを構築(最小限のメッセージとパラメータ)
    strRequestJson = "{""messages"": [{""role"": ""user"", ""content"": """ & _
                     Replace(queryPrompt, """", "\""") & _
                     """}], ""temperature"": 0.7, ""max_tokens"": 80}"

    ' WinHttpRequestオブジェクトの初期化
    Set httpRequest = CreateObject("WinHttp.WinHttpRequest.5.1")

    ' HTTPリクエストのオープン
    httpRequest.Open "POST", strURL, False ' False: 同期呼び出し

    ' ヘッダーの設定
    httpRequest.SetRequestHeader "Content-Type", "application/json"
    httpRequest.SetRequestHeader "api-key", AZURE_OPENAI_API_KEY

    ' リクエストの送信
    httpRequest.Send strRequestJson

    ' レスポンスの取得と表示
    If httpRequest.Status = 200 Then
        strResponseJson = httpRequest.ResponseText
        Debug.Print "Full Response JSON:"
        Debug.Print strResponseJson

        ' JSONレスポンスからコンテンツを簡易的に抽出(正規表現やJSONパーサーなし)
        Dim startPos As Long
        Dim endPos As Long
        Dim assistantContent As String

        startPos = InStr(strResponseJson, """content"": """)
        If startPos > 0 Then
            startPos = startPos + Len("""content"": """)
            endPos = InStr(startPos, strResponseJson, """")
            If endPos > startPos Then
                assistantContent = Mid(strResponseJson, startPos, endPos - startPos)
                Debug.Print "AI Answer:"
                Debug.Print assistantContent
                MsgBox assistantContent, vbInformation, "Azure OpenAI Answer"
            Else
                MsgBox "AIの応答コンテンツが見つかりませんでした。", vbExclamation
            End If
        Else
            MsgBox "AIの応答コンテンツが見つかりませんでした。", vbExclamation
        End If
    Else
        MsgBox "API呼び出し失敗: " & httpRequest.Status & " - " & httpRequest.StatusText & vbCrLf & _
               "Response: " & httpRequest.ResponseText, vbCritical
    End If

    Set httpRequest = Nothing
End Sub

内部動作補足: CreateObject("WinHttp.WinHttpRequest.5.1")はWindowsに組み込まれているCOMコンポーネントをロードします。OpenメソッドのFalse引数は同期処理を意味し、レスポンスが返るまでスクリプトの実行がブロックされます。これは小規模なタスクでは問題ありませんが、大規模な処理やUIを伴う場合は非同期処理(True)を検討すべきです。VBAでは非同期処理の実装が複雑になるため、ここでは同期処理を基本とします。

堅牢化 (VBA)

エラーハンドリング、プロキシ設定、タイムアウト、そしてJSONパースの補助クラス導入を考えます。外部ライブラリを避けるため、JSONパースには自作の軽量なクラスモジュール(例えば、JsonConverterのようなオープンソースの単一クラスモジュール)を前提としますが、ここではその詳細な実装は省略し、利用イメージを示します。

' このコードは、JsonConverter.cls (MITライセンス等で公開されている軽量なクラスモジュール)
' をプロジェクトにインポートしていることを前提とします。
' 外部ライブラリではないが、VBA標準機能ではないため補足。
' もしJsonConverterも使えない場合は、正規表現やInStr/Mid/Replaceを駆使して自力でパースすることになります。
' 例: Public Function GetJsonValue(jsonString As String, key As String) As String

'--- このファイルは "ModAzureOpenAI" などとして保存 ---

Private Const AZURE_OPENAI_RESOURCE_NAME As String = "my-aoai-resource"
Private Const AZURE_OPENAI_DEPLOYMENT_ID As String = "gpt-35-turbo-deploy"
Private Const AZURE_OPENAI_API_VERSION As String = "2023-05-15"
Private Const AZURE_OPENAI_API_KEY_ENV_VAR As String = "AZURE_OPENAI_API_KEY" ' APIキーの環境変数名

' プロキシ設定(必要に応じて設定)
Private Const USE_PROXY As Boolean = False
Private Const PROXY_SERVER As String = "http://your.proxy.server:8080"
Private Const PROXY_BYPASS As String = "<local>" ' ローカルアドレスはプロキシをバイパス

' タイムアウト設定 (ミリ秒)
Private Const REQUEST_TIMEOUT_MS As Long = 60000 ' 60秒

Sub CallAzureOpenAI_Robust()
    Dim httpRequest As WinHttp.WinHttpRequest ' 事前バインディングのために参照設定を推奨: "Microsoft WinHTTP Services, version 5.1"
    Dim strURL As String
    Dim strRequestJson As String
    Dim strResponseJson As String
    Dim queryPrompt As String
    Dim APIKey As String

    ' APIキーを環境変数から取得 (セキュリティ強化)
    APIKey = Environ(AZURE_OPENAI_API_KEY_ENV_VAR)
    If APIKey = "" Then
        MsgBox "環境変数 '" & AZURE_OPENAI_API_KEY_ENV_VAR & "' にAzure OpenAI APIキーが設定されていません。", vbCritical
        Exit Sub
    End If

    queryPrompt = InputBox("AIに質問を入力してください:", "Azure OpenAI Chat", "日本の首都は?")
    If queryPrompt = "" Then Exit Sub

    On Error GoTo ErrorHandler

    ' エンドポイントURLの構築
    strURL = "https://" & AZURE_OPENAI_RESOURCE_NAME & ".openai.azure.com/" & _
             "openai/deployments/" & AZURE_OPENAI_DEPLOYMENT_ID & "/chat/completions" & _
             "?api-version=" & AZURE_OPENAI_API_VERSION

    ' リクエストボディのJSONを構築 (ここではJsonConverterクラスの使用を想定)
    ' Set jsonDict = New Dictionary ' Scripting.Dictionary 参照設定が必要
    ' jsonDict("messages") = Array(CreateJsonMessage("user", queryPrompt))
    ' jsonDict("temperature") = 0.7
    ' jsonDict("max_tokens") = 80
    ' strRequestJson = JsonConverter.ConvertToJson(jsonDict, 2) ' 第2引数はPrettyPrint

    ' 軽量クラスを使わない場合、以下のように手動で生成する
    strRequestJson = "{""messages"": [{""role"": ""user"", ""content"": """ & _
                     Replace(queryPrompt, """", "\""") & _
                     """}], ""temperature"": 0.7, ""max_tokens"": 80}"

    Set httpRequest = New WinHttp.WinHttpRequest

    ' プロキシ設定
    If USE_PROXY Then
        httpRequest.SetProxy 2, PROXY_SERVER, PROXY_BYPASS ' 2: HTTP_PROXY_TYPE_AUTO_PROXY
    End If

    ' タイムアウト設定 (Openの後、Sendの前に設定)
    httpRequest.SetTimeouts REQUEST_TIMEOUT_MS, REQUEST_TIMEOUT_MS, REQUEST_TIMEOUT_MS, REQUEST_TIMEOUT_MS

    httpRequest.Open "POST", strURL, False

    ' ヘッダーの設定
    httpRequest.SetRequestHeader "Content-Type", "application/json"
    httpRequest.SetRequestHeader "api-key", APIKey

    httpRequest.Send strRequestJson

    ' ステータスコードのチェック
    If httpRequest.Status >= 200 And httpRequest.Status < 300 Then
        strResponseJson = httpRequest.ResponseText
        Debug.Print "Full Response JSON:"
        Debug.Print strResponseJson

        ' JSONレスポンスのパース (JsonConverterクラスの使用を想定)
        ' Dim parsedJson As Object
        ' Set parsedJson = JsonConverter.ParseJson(strResponseJson)
        ' Dim assistantContent As String
        ' assistantContent = parsedJson("choices")(1)("message")("content") ' VBAのDictionary/Collectionは1ベースインデックスの可能性あり

        ' 軽量クラスを使わない場合、正規表現などで抽出する
        Dim regEx As Object
        Dim matches As Object
        Dim assistantContent As String
        Set regEx = CreateObject("VBScript.RegExp")
        regEx.Pattern = """content"":\s*""([^""]*)"""
        regEx.Global = False ' 最初のマッチのみ
        Set matches = regEx.Execute(strResponseJson)
        If matches.Count > 0 Then
            assistantContent = matches(0).SubMatches(0)
            Debug.Print "AI Answer:"
            Debug.Print assistantContent
            MsgBox assistantContent, vbInformation, "Azure OpenAI Answer"
        Else
            MsgBox "AIの応答コンテンツが見つかりませんでした。", vbExclamation
        End If

    Else
        Dim errorMessage As String
        errorMessage = "API呼び出し失敗: " & httpRequest.Status & " - " & httpRequest.StatusText & vbCrLf & _
                       "Response: " & httpRequest.ResponseText
        MsgBox errorMessage, vbCritical
        Debug.Print "Error Response: " & httpRequest.ResponseText
    End If

CleanUp:
    Set httpRequest = Nothing
    Exit Sub

ErrorHandler:
    MsgBox "ランタイムエラーが発生しました: " & Err.Description, vbCritical
    Debug.Print "Error Number: " & Err.Number
    Debug.Print "Error Description: " & Err.Description
    Resume CleanUp
End Sub

' JsonConverterを使わない場合の簡易的なメッセージ構築ヘルパー(例)
Private Function CreateJsonMessage(role As String, content As String) As String
    CreateJsonMessage = "{""role"": """ & role & """, ""content"": """ & Replace(content, """", "\""") & """}"
End Function

WinHttpRequestと64bit/PtrSafe/LongPtrについて: WinHttpRequestオブジェクトはCOMコンポーネントであり、VBAから参照設定することで「早期バインディング」が可能になります(Dim httpRequest As WinHttp.WinHttpRequest)。これは実行時エラーの早期発見やコード補完の恩恵を受けられます。CreateObjectを使う「レイトバインディング」は参照設定不要ですが、わずかにパフォーマンスが劣ります。PtrSafeLongPtrは、主に32bit/64bit環境でDLL関数を直接呼び出すDeclareステートメントで使用されるキーワードです。WinHttpRequest自体はCOMオブジェクトとして抽象化されているため、直接的なPtrSafeは不要ですが、VBAのLong型は32bit整数であるため、64bit環境でポインタや非常に大きな数値を扱う場合はLongPtrLongLongを使用する必要があります。APIキーを環境変数から取得することで、コード内にハードコーディングするリスクを低減し、セキュリティを向上させています。

PowerShell実装

PowerShellではInvoke-RestMethodコマンドレットを使用することで、HTTPリクエストの送信、JSONの変換、レスポンスのオブジェクト化までを非常に簡潔に記述できます。

最小実装 (PowerShell)

#--- このファイルは "CallAzureOpenAI.ps1" などとして保存 ---

$AzureOpenAIResourceName = "my-aoai-resource" # あなたのリソース名
$AzureOpenAIDeploymentId = "gpt-35-turbo-deploy" # あなたのデプロイID
$AzureOpenAIAPIVersion = "2023-05-15" # 使用するAPIバージョン
$AzureOpenAIAPIKey = "YOUR_SUPER_SECRET_API_KEY" # あなたのAPIキー

$queryPrompt = Read-Host -Prompt "AIに質問を入力してください"
if ([string]::IsNullOrWhiteSpace($queryPrompt)) {
    Write-Warning "質問が入力されませんでした。処理を終了します。"
    exit
}

# エンドポイントURLの構築
$uri = "https://$($AzureOpenAIResourceName).openai.azure.com/openai/deployments/$($AzureOpenAIDeploymentId)/chat/completions?api-version=$($AzureOpenAIAPIVersion)"

# ヘッダーの設定
$headers = @{
    "Content-Type" = "application/json"
    "api-key"      = $AzureOpenAIAPIKey
}

# リクエストボディのJSONを構築
$body = @{
    messages = @(
        @{
            role    = "user"
            content = $queryPrompt
        }
    )
    temperature = 0.7
    max_tokens  = 80
} | ConvertTo-Json

Write-Host "Sending request to: $uri"
Write-Host "Request body: $($body)"

try {
    # HTTPリクエストの送信
    $response = Invoke-RestMethod -Uri $uri -Method Post -Headers $headers -Body $body

    Write-Host "Full Response:"
    $response | ConvertTo-Json -Depth 5 | Write-Host # -Depthでネストされたオブジェクトも表示

    # 応答からコンテンツを抽出
    $assistantContent = $response.choices[0].message.content
    Write-Host "`nAI Answer:"
    Write-Host $assistantContent
    [System.Windows.Forms.MessageBox]::Show($assistantContent, "Azure OpenAI Answer", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information) | Out-Null
}
catch {
    Write-Error "API呼び出し失敗: $($_.Exception.Message)"
    if ($_.Exception.Response) {
        $errorResponse = $_.Exception.Response.GetResponseStream()
        $reader = New-Object System.IO.StreamReader($errorResponse)
        $responseBody = $reader.ReadToEnd()
        Write-Error "Error Response Body: $responseBody"
    }
}

内部動作補足: Invoke-RestMethodは.NETのHttpClientクラスをラップしており、HTTPリクエストの送信、JSONの自動シリアライズ/デシリアライズ、エラーハンドリング(HTTPステータスコードがエラーの場合に例外をスロー)を統合して提供します。ConvertTo-JsonはPowerShellのハッシュテーブルやオブジェクトをJSON文字列に変換し、Invoke-RestMethodは返されたJSON文字列を自動的にPowerShellオブジェクトに変換します。

堅牢化 (PowerShell)

APIキーのSecureString化、プロキシ設定、タイムアウト、エラー処理の強化などを組み込みます。

#--- このファイルは "CallAzureOpenAI_Robust.ps1" などとして保存 ---

# パラメータ定義 (スクリプトの先頭で定義することで、呼び出し時に指定可能にする)
[CmdletBinding()]
param(
    [string]$AzureOpenAIResourceName = "my-aoai-resource", # あなたのリソース名
    [string]$AzureOpenAIDeploymentId = "gpt-35-turbo-deploy", # あなたのデプロイID
    [string]$AzureOpenAIAPIVersion = "2023-05-15", # 使用するAPIバージョン
    [string]$AzureOpenAIAPIKeyEnvVar = "AZURE_OPENAI_API_KEY", # APIキーの環境変数名
    [int]$RequestTimeoutSeconds = 120, # タイムアウト秒数
    [string]$ProxyServer = $null, # プロキシサーバーのURL (例: "http://your.proxy.server:8080")
    [switch]$UseSecureStringApiKey # APIキーをSecureStringとしてプロンプトから入力するか
)

# APIキーの取得(環境変数またはSecureString)
$apiKey = ""
if ($UseSecureStringApiKey) {
    # SecureStringとして安全に入力プロンプトから取得
    $secureApiKey = Read-Host -Prompt "Azure OpenAI API Key" -AsSecureString
    $apiKey = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureApiKey))
} else {
    # 環境変数から取得
    $apiKey = $env:$AzureOpenAIAPIKeyEnvVar
    if ([string]::IsNullOrWhiteSpace($apiKey)) {
        Write-Error "環境変数 '$AzureOpenAIAPIKeyEnvVar' にAzure OpenAI APIキーが設定されていません。または -UseSecureStringApiKey を使用してください。"
        exit 1
    }
}

$queryPrompt = Read-Host -Prompt "AIに質問を入力してください"
if ([string]::IsNullOrWhiteSpace($queryPrompt)) {
    Write-Warning "質問が入力されませんでした。処理を終了します。"
    exit
}

# エンドポイントURLの構築
$uri = "https://$($AzureOpenAIResourceName).openai.azure.com/openai/deployments/$($AzureOpenAIDeploymentId)/chat/completions?api-version=$($AzureOpenAIAPIVersion)"

# ヘッダーの設定
$headers = @{
    "Content-Type" = "application/json"
    "api-key"      = $apiKey
}

# リクエストボディのJSONを構築
$body = @{
    messages = @(
        @{
            role    = "user"
            content = $queryPrompt
        }
    )
    temperature = 0.7
    max_tokens  = 200 # 応答の最大トークン数を増やす
    # top_p       = 0.9 # 追加のパラメータ
    # stop        = @("Human:", "AI:") # 停止シーケンス
} | ConvertTo-Json -Compress # -Compress で空白を削除し、ペイロードサイズを最適化

Write-Verbose "Sending request to: $uri"
Write-Verbose "Request body: $($body)"

try {
    $invokeRestMethodParams = @{
        Uri     = $uri
        Method  = "Post"
        Headers = $headers
        Body    = $body
        TimeoutSec = $RequestTimeoutSeconds
        ContentType = "application/json" # 明示的に指定
    }

    if ($null -ne $ProxyServer) {
        $invokeRestMethodParams.Add("Proxy", $ProxyServer)
    }

    # HTTPリクエストの送信
    $response = Invoke-RestMethod @invokeRestMethodParams -ErrorAction Stop

    Write-Host "Full Response:"
    $response | ConvertTo-Json -Depth 5 | Write-Host

    $assistantContent = $response.choices[0].message.content
    Write-Host "`nAI Answer:"
    Write-Host $assistantContent
    [System.Windows.Forms.MessageBox]::Show($assistantContent, "Azure OpenAI Answer", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information) | Out-Null
}
catch [System.Net.WebException] {
    # HTTPレベルのエラー (4xx, 5xx)
    $statusCode = $_.Exception.Response.StatusCode.value__
    $statusDescription = $_.Exception.Response.StatusDescription
    Write-Error "HTTP API呼び出し失敗: $($statusCode) - $($statusDescription)"
    if ($_.Exception.Response) {
        $errorResponse = $_.Exception.Response.GetResponseStream()
        $reader = New-Object System.IO.StreamReader($errorResponse)
        $responseBody = $reader.ReadToEnd()
        Write-Error "Error Response Body: $responseBody"
    }
}
catch [System.Exception] {
    # その他の予期せぬエラー
    Write-Error "予期せぬエラーが発生しました: $($_.Exception.Message)"
    Write-Error "$($_.ScriptStackTrace)"
}
finally {
    # SecureStringから取得したAPIキーのメモリ解放 (重要)
    if ($UseSecureStringApiKey) {
        [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureApiKey))
        $secureApiKey.Dispose() # SecureStringオブジェクトも破棄
    }
}

セキュリティとPowerShell: APIキーをRead-Host -AsSecureStringで取得し、System.Runtime.InteropServices.Marshalを使って一時的に平文に戻す方法は、スクリプト実行中にAPIキーがメモリ上に平文で存在する時間を最小限に抑えるための一般的な手法です。finallyブロックでZeroFreeBSTRDisposeを呼び出すことで、APIキーのメモリを安全に解放しています。これはセキュリティの観点から非常に重要です。$env:プレフィックスで環境変数からAPIキーを取得することも可能で、こちらの方がスクリプトに平文を一切含めないという点でさらに推奨されます。

ベンチ/検証

AI連携のスクリプトでは、単に動作するだけでなく、パフォーマンスや堅牢性も重要です。

計測方法

VBA: Timer関数で開始時刻と終了時刻を取得し、差分を計算します。

Dim startTime As Double
Dim endTime As Double
startTime = Timer
' ここにAPI呼び出しコード
endTime = Timer
Debug.Print "経過時間: " & (endTime - startTime) & "秒"

PowerShell: Measure-Commandコマンドレットを利用すると簡潔です。

Measure-Command {
    # ここにAPI呼び出しコード
}

テスト観点

  1. 応答時間 (Latency):
    • 短いプロンプトと長いプロンプトでの応答時間を計測。
    • max_tokensを小さく設定した場合と大きく設定した場合での変化。
    • ネットワーク状況(プロキシの有無、帯域)による変動。
  2. エラーハンドリング:
    • 無効なAPIキー、無効なデプロイID、不正なJSONボディを意図的に送信し、適切にエラーが捕捉され、情報がログに出力されるかを確認。
    • タイムアウト設定を超過するような長時間応答の場合、スクリプトがハングせずタイムアウトエラーを適切に処理するか。
  3. トークン使用量:
    • 同じプロンプトでもtemperaturetop_pなどのパラメータを変えた場合、生成されるトークン数にどの程度影響が出るか。
    • Azureポータルのメトリックで、実際にかかったトークン数が想定と一致するか確認。
  4. プロンプトの堅牢性:
    • 特殊文字(JSONエスケープが必要な文字)を含むプロンプトが正しく処理されるか。
    • 長すぎるプロンプトがモデルのコンテキストウィンドウを超過した場合、APIが適切にエラーを返すか(通常は400 Bad Request)。

応用例/代替案

応用例

  • Excel VBA:
    • 既存のデータ分析シートにAI要約機能を追加。例えば、顧客コメントのセル範囲を選択し、要約ボタンを押すと別セルに要約結果が出力される。
    • 定型的な報告書の下書き生成。
    • 多言語対応(翻訳)。
  • PowerShell:
    • ログファイルから異常検知の要約を生成し、レポートとして出力。
    • Active Directoryユーザー情報からプロファイル記述を自動生成。
    • インシデント管理システムへの自動チケット起票スクリプトに、AIによる事象分析を追加。

代替案

  1. Azure Functions / Logic Apps:
    • VBA/PowerShellスクリプトが直接Azure OpenAI APIを叩くのではなく、Azure FunctionsやLogic Appsを中継役として挟む。
    • 利点: APIキーの管理を一元化し、ロジックをクラウド側で集中管理できる。VBA/PowerShell側はよりシンプルなHTTP呼び出しになる。レートリミット管理なども容易。
    • 欠点: 追加のAzureリソースと費用、開発・デプロイの手間。
  2. Python等モダン言語の活用:
    • OpenAI公式のPythonライブラリやrequestsのようなHTTPクライアントライブラリを利用すれば、JSONの構築やパースがより容易で堅牢。
    • VBA/PowerShellからはPythonスクリプトを呼び出す形にする。
    • 利点: 開発効率向上、豊富なエコシステム。
    • 欠点: 実行環境にPythonのインストールが必要、VBA/PowerShellとの連携オーバーヘッド。
  3. ローカルLLM (Ollamaなど) との連携:
    • 機密性の高いデータやネットワーク接続が制限される環境では、ローカルで動作するLLM(例: Ollama)を検討。
    • OllamaはローカルにAPIサーバーを構築し、OpenAI互換のAPIを提供するため、今回紹介したREST API連携の知識が活かせる。
    • 利点: データプライバシーの確保、ネットワーク遅延の低減。
    • 欠点: ローカルリソース(GPU/CPU/メモリ)の消費、モデルの性能や選択肢の制約。

失敗例→原因→対処

ケーススタディ: 401 Unauthorized / 404 Not Found エラー

失敗例: VBAまたはPowerShellスクリプトを実行すると、401 Unauthorizedまたは404 Not Foundエラーが返される。レスポンスボディには「アクセスが拒否されました」「デプロイが見つかりません」といったメッセージが含まれる。

原因: これらのエラーは、認証情報またはリソースの指定に問題がある場合に頻繁に発生します。 1. APIキーの誤り: * キーが間違っている、有効期限が切れている、または誤ったキータイプ(例: リソースのAPIキーではなく別のサービスのキー)を使用している。 * ヘッダー名がapi-keyではなくAuthorizationなど、誤ったものを指定している。 2. エンドポイントURLの誤り: * {your-resource-name}(Azure OpenAIリソース名)のスペルミス。 * {your-deployment-id}(モデルのデプロイ名)のスペルミス、またはデプロイ自体が存在しない。 * Azure OpenAIリソースが存在するリージョンと、URLで指定しているリージョンが一致しない(通常、リソース名でリージョンは暗黙的に決まるが、カスタムドメインなどを利用している場合は注意)。 * api-versionが古い、または存在しないバージョンを指定している。 3. ネットワークまたはプロキシの問題: * 企業内ネットワークでプロキシ設定が正しく行われていない場合、そもそもAzure OpenAI Serviceに到達できない。 * ファイアウォールでAzure OpenAI Serviceのエンドポイントがブロックされている。

対処: 1. APIキーの確認: * Azureポータルで該当のAzure OpenAIリソースにアクセスし、「キーとエンドポイント」ブレードから正しいAPIキー(Key 1またはKey 2)をコピーし、スクリプトに設定されているキーと比較確認します。 * VBA/PowerShellスクリプトでSetRequestHeader "api-key", YOUR_API_KEYが正確に記述されているか確認します。 2. エンドポイントURLの確認: * Azureポータルの「キーとエンドポイント」ブレードに表示される「エンドポイント」URLを正確にコピーし、スクリプト内のstrURL$uriと照合します。 * 特にdeployments/{your-deployment-id}の部分のyour-deployment-idが、実際にデプロイしたモデルに付けた「デプロイ名」と一致するか確認します。model名(例: gpt-3.5-turbo)を直接使うのではなく、デプロイ名を使います。 * api-versionもポータルや公式ドキュメントで最新かつ推奨されているバージョンを確認して設定します。 3. ネットワーク/プロキシ設定の確認: * VBAのWinHttpRequestではSetProxyメソッド、PowerShellのInvoke-RestMethodでは-Proxyパラメータを適切に設定しているか確認します。 * 必要であれば、社内IT部門に相談し、Azure OpenAI Serviceへのアウトバウンド接続が許可されているか確認します。

これらの確認を徹底することで、認証とエンドポイントに関するエラーのほとんどは解決できます。

まとめ

VBAやPowerShellといった既存の業務自動化基盤からAzure OpenAI Serviceの力を引き出すことは、適切な知識と実装さえあれば十分に可能です。本稿では、WinHttpRequestInvoke-RestMethodを用いたHTTPリクエストの基本から、JSONリクエスト/レスポンスの構築と解析、そしてAPIキー認証やエンドポイントの厳密な指定といった内部動作まで踏み込みました。

最小限の実装から始まり、エラーハンドリング、プロキシ設定、セキュリティを考慮したAPIキーの管理へと堅牢化する過程を通じて、実務で安心して使えるスクリプトの作成指針を示しました。また、ベンチマークの観点や、よくある失敗例とその対処法についても解説することで、あなたがAI連携の荒波を乗り越えるための羅針盤となることを目指しました。

この知識を基盤として、既存のVBA/PowerShell資産にAIの知見を注入し、業務効率化や新たな価値創造へと繋げていただければ幸いです。

運用チェックリスト

  • APIキーの管理とローテーション:
    • APIキーはコードに直接埋め込まず、環境変数やセキュアな設定ファイルで管理しているか?
    • 定期的なAPIキーのローテーション手順は確立されているか?
  • エンドポイントURLの変更対応:
    • 将来的なAPIバージョンアップやリソース移転に備え、URLは設定値として外部化されているか?
  • プロンプトエンジニアリングの継続的な改善:
    • 生成されるAIの応答品質を向上させるため、プロンプトの設計は定期的に見直されているか?
  • レスポンスエラーハンドリングの徹底:
    • HTTPステータスコード(4xx, 5xx)だけでなく、APIから返されるエラーレスポンスボディも適切に解析・ログ出力されているか?
    • リトライロジックやフォールバック処理は考慮されているか?
  • 課金状況の監視:
    • AzureポータルでAzure OpenAI Serviceの利用状況(トークン数、コスト)を定期的に確認し、予期せぬ高額請求を防ぐ体制があるか?
  • タイムアウト設定の適切性:
    • API呼び出しのタイムアウトは、業務要件とAPIの応答特性に合わせて適切に設定されているか?
  • プロキシ環境への対応:
    • 企業内のプロキシサーバーを経由する場合、スクリプトはプロキシ設定に正しく対応しているか?
  • ログ出力の設計:
    • APIの呼び出し時刻、リクエスト内容(機密情報はマスク)、レスポンス、エラー情報が適切にログに出力され、トラブルシューティングに役立つか?

参考リンク

ライセンス:本記事のテキスト/コードは特記なき限り CC BY 4.0 です。引用の際は出典URL(本ページ)を明記してください。
利用ポリシー もご参照ください。

コメント

タイトルとURLをコピーしました