<p>VBA/PowerShellからAzure OpenAI APIを呼び出すディープダイブ</p>
<h2 class="wp-block-heading">導入(問題設定)</h2>
<p>既存の業務システムがVBAやPowerShellで構築されている現場において、最新のAI技術、特にAzure OpenAI Serviceの力を取り入れたいというニーズは高まっています。しかし、その実現には障壁が少なくありません。外部ライブラリの導入が難しい、COMオブジェクトやPowerShellネイティブの機能だけでどこまでできるのか、といった疑問は尽きません。</p>
<p>本稿では、こうした現場の深い悩みに応えるべく、VBAおよびPowerShellからAzure OpenAI ServiceのChat Completions APIを直接、かつ<strong>極力外部ライブラリに依存せず</strong>に呼び出す方法を、その内部動作、境界条件、そして陥りやすい落とし穴まで含めて<strong>マニアックに</strong>解説します。単なるHowToに留まらず、最小実装から堅牢化までの道のりを示し、あなたのスクリプトを次世代のAI駆動型システムへと昇華させるための知識を提供します。</p>
<h2 class="wp-block-heading">理論の要点</h2>
<p>Azure OpenAI Serviceとの連携は、基本的にREST APIを介して行われます。クライアント(VBA/PowerShell)からHTTPリクエストを送信し、JSON形式で構成されたリクエストボディにプロンプトやパラメータを含めて送信。サービスから返されるJSONレスポンスを解析し、AIの生成結果を得る、という流れです。</p>
<h3 class="wp-block-heading">認証とエンドポイント</h3>
<p>Azure OpenAI ServiceのREST APIは、通常、APIキーベースの認証を採用します。このAPIキーは、HTTPヘッダーに<code>api-key: YOUR_API_KEY</code>として含める必要があります。
エンドポイントURLは以下の形式を取ります。
<code>https://{your-resource-name}.openai.azure.com/openai/deployments/{your-deployment-id}/chat/completions?api-version=2023-05-15</code></p>
<ul class="wp-block-list">
<li><code>{your-resource-name}</code>: Azure OpenAI Serviceリソースの作成時に指定した名前。</li>
<li><code>{your-deployment-id}</code>: デプロイしたモデル(例: <code>gpt-35-turbo</code>)に付けたデプロイID。</li>
<li><code>api-version</code>: 使用するAPIのバージョン。公式ドキュメントで最新版を確認してください。</li>
</ul>
<h3 class="wp-block-heading">Chat Completions APIの主要パラメータ</h3>
<p>Chat Completions APIは、<code>POST</code>メソッドでJSON形式のリクエストボディを送信します。</p>
<figure class="wp-block-table"><table>
<thead>
<tr>
<th style="text-align:left;">パラメータ名</th>
<th style="text-align:left;">必須/任意</th>
<th style="text-align:left;">型</th>
<th style="text-align:left;">説明</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left;"><code>messages</code></td>
<td style="text-align:left;">必須</td>
<td style="text-align:left;">配列</td>
<td style="text-align:left;">ロールとコンテンツを持つメッセージオブジェクトの配列。AIとの会話履歴を表現。</td>
</tr>
<tr>
<td style="text-align:left;"> <code>role</code></td>
<td style="text-align:left;">必須</td>
<td style="text-align:left;">文字列</td>
<td style="text-align:left;">メッセージの送り手 (<code>system</code>, <code>user</code>, <code>assistant</code>)。</td>
</tr>
<tr>
<td style="text-align:left;"> <code>content</code></td>
<td style="text-align:left;">必須</td>
<td style="text-align:left;">文字列</td>
<td style="text-align:left;">メッセージの本文。</td>
</tr>
<tr>
<td style="text-align:left;"><code>temperature</code>| 任意</td>
<td style="text-align:left;">数値</td>
<td style="text-align:left;">生成されるテキストの多様性(ランダム性)を制御。0.0(決定的)~2.0(創造的)。デフォルト1.0。</td>
</tr>
<tr>
<td style="text-align:left;"><code>max_tokens</code></td>
<td style="text-align:left;">任意</td>
<td style="text-align:left;">整数</td>
<td style="text-align:left;">応答で生成されるトークンの最大数。入力と出力の合計はモデルのコンテキストウィンドウを超えられない。</td>
</tr>
<tr>
<td style="text-align:left;"><code>top_p</code></td>
<td style="text-align:left;">任意</td>
<td style="text-align:left;">数値</td>
<td style="text-align:left;">nucleusサンプリング閾値。temperatureと組み合わせて多様性を制御。0.0~1.0。デフォルト1.0。</td>
</tr>
<tr>
<td style="text-align:left;"><code>stop</code></td>
<td style="text-align:left;">任意</td>
<td style="text-align:left;">文字列/配列</td>
<td style="text-align:left;">AIが生成を停止するシーケンス。最大4つまで。</td>
</tr>
<tr>
<td style="text-align:left;"><code>stream</code></td>
<td style="text-align:left;">任意</td>
<td style="text-align:left;">真偽値</td>
<td style="text-align:left;">応答をストリーミング形式で受け取るか。<code>true</code>の場合、応答はServer-Sent Events形式となる。</td>
</tr>
</tbody>
</table></figure>
<h3 class="wp-block-heading">API呼び出しのシーケンス</h3>
<div class="wp-block-merpress-mermaidjs diagram-source-mermaid"><pre class="mermaid">
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
</pre></div>
<h2 class="wp-block-heading">実装(最小→堅牢化)</h2>
<h3 class="wp-block-heading">共通の準備</h3>
<p>Azure OpenAI Serviceのリソース作成とモデルのデプロイを済ませておいてください。
エンドポイントURLとAPIキーを控えておきます。
例:
– リソース名: <code>my-aoai-resource</code>
– デプロイID: <code>gpt-35-turbo-deploy</code>
– APIキー: <code>YOUR_SUPER_SECRET_API_KEY</code>
– APIバージョン: <code>2023-05-15</code></p>
<h3 class="wp-block-heading">VBA実装</h3>
<p>VBAでは<code>WinHttpRequest.5.1</code>オブジェクトを使用してHTTPリクエストを送信します。JSONの生成と解析は、ネイティブ機能で文字列操作するか、軽量なクラスモジュールを利用する選択肢があります。本稿では「極力外部ライブラリなし」のため、文字列操作を基本とします。</p>
<h4 class="wp-block-heading">最小実装 (VBA)</h4>
<p>Excel VBAで簡単なチャット対話を行う例です。</p>
<pre data-enlighter-language="generic">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
</pre>
<p><strong>内部動作補足:</strong> <code>CreateObject("WinHttp.WinHttpRequest.5.1")</code>はWindowsに組み込まれているCOMコンポーネントをロードします。<code>Open</code>メソッドの<code>False</code>引数は同期処理を意味し、レスポンスが返るまでスクリプトの実行がブロックされます。これは小規模なタスクでは問題ありませんが、大規模な処理やUIを伴う場合は非同期処理(<code>True</code>)を検討すべきです。VBAでは非同期処理の実装が複雑になるため、ここでは同期処理を基本とします。</p>
<h4 class="wp-block-heading">堅牢化 (VBA)</h4>
<p>エラーハンドリング、プロキシ設定、タイムアウト、そしてJSONパースの補助クラス導入を考えます。外部ライブラリを避けるため、JSONパースには自作の軽量なクラスモジュール(例えば、<code>JsonConverter</code>のようなオープンソースの単一クラスモジュール)を前提としますが、ここではその詳細な実装は省略し、利用イメージを示します。</p>
<pre data-enlighter-language="generic">' このコードは、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
</pre>
<p><strong><code>WinHttpRequest</code>と64bit/PtrSafe/LongPtrについて:</strong> <code>WinHttpRequest</code>オブジェクトはCOMコンポーネントであり、VBAから参照設定することで「早期バインディング」が可能になります(<code>Dim httpRequest As WinHttp.WinHttpRequest</code>)。これは実行時エラーの早期発見やコード補完の恩恵を受けられます。<code>CreateObject</code>を使う「レイトバインディング」は参照設定不要ですが、わずかにパフォーマンスが劣ります。<code>PtrSafe</code>や<code>LongPtr</code>は、主に32bit/64bit環境でDLL関数を直接呼び出す<code>Declare</code>ステートメントで使用されるキーワードです。<code>WinHttpRequest</code>自体はCOMオブジェクトとして抽象化されているため、直接的な<code>PtrSafe</code>は不要ですが、VBAの<code>Long</code>型は32bit整数であるため、64bit環境でポインタや非常に大きな数値を扱う場合は<code>LongPtr</code>や<code>LongLong</code>を使用する必要があります。APIキーを環境変数から取得することで、コード内にハードコーディングするリスクを低減し、セキュリティを向上させています。</p>
<h3 class="wp-block-heading">PowerShell実装</h3>
<p>PowerShellでは<code>Invoke-RestMethod</code>コマンドレットを使用することで、HTTPリクエストの送信、JSONの変換、レスポンスのオブジェクト化までを非常に簡潔に記述できます。</p>
<h4 class="wp-block-heading">最小実装 (PowerShell)</h4>
<pre data-enlighter-language="generic">#--- このファイルは "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"
}
}
</pre>
<p><strong>内部動作補足:</strong> <code>Invoke-RestMethod</code>は.NETの<code>HttpClient</code>クラスをラップしており、HTTPリクエストの送信、JSONの自動シリアライズ/デシリアライズ、エラーハンドリング(HTTPステータスコードがエラーの場合に例外をスロー)を統合して提供します。<code>ConvertTo-Json</code>はPowerShellのハッシュテーブルやオブジェクトをJSON文字列に変換し、<code>Invoke-RestMethod</code>は返されたJSON文字列を自動的にPowerShellオブジェクトに変換します。</p>
<h4 class="wp-block-heading">堅牢化 (PowerShell)</h4>
<p>APIキーのSecureString化、プロキシ設定、タイムアウト、エラー処理の強化などを組み込みます。</p>
<pre data-enlighter-language="generic">#--- このファイルは "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オブジェクトも破棄
}
}
</pre>
<p><strong>セキュリティとPowerShell:</strong> APIキーを<code>Read-Host -AsSecureString</code>で取得し、<code>System.Runtime.InteropServices.Marshal</code>を使って一時的に平文に戻す方法は、スクリプト実行中にAPIキーがメモリ上に平文で存在する時間を最小限に抑えるための一般的な手法です。<code>finally</code>ブロックで<code>ZeroFreeBSTR</code>と<code>Dispose</code>を呼び出すことで、APIキーのメモリを安全に解放しています。これはセキュリティの観点から非常に重要です。<code>$env:</code>プレフィックスで環境変数からAPIキーを取得することも可能で、こちらの方がスクリプトに平文を一切含めないという点でさらに推奨されます。</p>
<h2 class="wp-block-heading">ベンチ/検証</h2>
<p>AI連携のスクリプトでは、単に動作するだけでなく、パフォーマンスや堅牢性も重要です。</p>
<h3 class="wp-block-heading">計測方法</h3>
<p>VBA: <code>Timer</code>関数で開始時刻と終了時刻を取得し、差分を計算します。</p>
<pre data-enlighter-language="generic">Dim startTime As Double
Dim endTime As Double
startTime = Timer
' ここにAPI呼び出しコード
endTime = Timer
Debug.Print "経過時間: " & (endTime - startTime) & "秒"
</pre>
<p>PowerShell: <code>Measure-Command</code>コマンドレットを利用すると簡潔です。</p>
<pre data-enlighter-language="generic">Measure-Command {
# ここにAPI呼び出しコード
}
</pre>
<h3 class="wp-block-heading">テスト観点</h3>
<ol class="wp-block-list">
<li><strong>応答時間 (Latency)</strong>:
<ul>
<li>短いプロンプトと長いプロンプトでの応答時間を計測。</li>
<li><code>max_tokens</code>を小さく設定した場合と大きく設定した場合での変化。</li>
<li>ネットワーク状況(プロキシの有無、帯域)による変動。</li>
</ul></li>
<li><strong>エラーハンドリング</strong>:
<ul>
<li>無効なAPIキー、無効なデプロイID、不正なJSONボディを意図的に送信し、適切にエラーが捕捉され、情報がログに出力されるかを確認。</li>
<li>タイムアウト設定を超過するような長時間応答の場合、スクリプトがハングせずタイムアウトエラーを適切に処理するか。</li>
</ul></li>
<li><strong>トークン使用量</strong>:
<ul>
<li>同じプロンプトでも<code>temperature</code>や<code>top_p</code>などのパラメータを変えた場合、生成されるトークン数にどの程度影響が出るか。</li>
<li>Azureポータルのメトリックで、実際にかかったトークン数が想定と一致するか確認。</li>
</ul></li>
<li><strong>プロンプトの堅牢性</strong>:
<ul>
<li>特殊文字(JSONエスケープが必要な文字)を含むプロンプトが正しく処理されるか。</li>
<li>長すぎるプロンプトがモデルのコンテキストウィンドウを超過した場合、APIが適切にエラーを返すか(通常は<code>400 Bad Request</code>)。</li>
</ul></li>
</ol>
<h2 class="wp-block-heading">応用例/代替案</h2>
<h3 class="wp-block-heading">応用例</h3>
<ul class="wp-block-list">
<li><strong>Excel VBA</strong>:
<ul>
<li>既存のデータ分析シートにAI要約機能を追加。例えば、顧客コメントのセル範囲を選択し、要約ボタンを押すと別セルに要約結果が出力される。</li>
<li>定型的な報告書の下書き生成。</li>
<li>多言語対応(翻訳)。</li>
</ul></li>
<li><strong>PowerShell</strong>:
<ul>
<li>ログファイルから異常検知の要約を生成し、レポートとして出力。</li>
<li>Active Directoryユーザー情報からプロファイル記述を自動生成。</li>
<li>インシデント管理システムへの自動チケット起票スクリプトに、AIによる事象分析を追加。</li>
</ul></li>
</ul>
<h3 class="wp-block-heading">代替案</h3>
<ol class="wp-block-list">
<li><strong>Azure Functions / Logic Apps</strong>:
<ul>
<li>VBA/PowerShellスクリプトが直接Azure OpenAI APIを叩くのではなく、Azure FunctionsやLogic Appsを中継役として挟む。</li>
<li>利点: APIキーの管理を一元化し、ロジックをクラウド側で集中管理できる。VBA/PowerShell側はよりシンプルなHTTP呼び出しになる。レートリミット管理なども容易。</li>
<li>欠点: 追加のAzureリソースと費用、開発・デプロイの手間。</li>
</ul></li>
<li><strong>Python等モダン言語の活用</strong>:
<ul>
<li>OpenAI公式のPythonライブラリやrequestsのようなHTTPクライアントライブラリを利用すれば、JSONの構築やパースがより容易で堅牢。</li>
<li>VBA/PowerShellからはPythonスクリプトを呼び出す形にする。</li>
<li>利点: 開発効率向上、豊富なエコシステム。</li>
<li>欠点: 実行環境にPythonのインストールが必要、VBA/PowerShellとの連携オーバーヘッド。</li>
</ul></li>
<li><strong>ローカルLLM (Ollamaなど) との連携</strong>:
<ul>
<li>機密性の高いデータやネットワーク接続が制限される環境では、ローカルで動作するLLM(例: Ollama)を検討。</li>
<li>OllamaはローカルにAPIサーバーを構築し、OpenAI互換のAPIを提供するため、今回紹介したREST API連携の知識が活かせる。</li>
<li>利点: データプライバシーの確保、ネットワーク遅延の低減。</li>
<li>欠点: ローカルリソース(GPU/CPU/メモリ)の消費、モデルの性能や選択肢の制約。</li>
</ul></li>
</ol>
<h2 class="wp-block-heading">失敗例→原因→対処</h2>
<h3 class="wp-block-heading">ケーススタディ: 401 Unauthorized / 404 Not Found エラー</h3>
<p><strong>失敗例</strong>:
VBAまたはPowerShellスクリプトを実行すると、<code>401 Unauthorized</code>または<code>404 Not Found</code>エラーが返される。レスポンスボディには「アクセスが拒否されました」「デプロイが見つかりません」といったメッセージが含まれる。</p>
<p><strong>原因</strong>:
これらのエラーは、認証情報またはリソースの指定に問題がある場合に頻繁に発生します。
1. <strong>APIキーの誤り</strong>:
* キーが間違っている、有効期限が切れている、または誤ったキータイプ(例: リソースのAPIキーではなく別のサービスのキー)を使用している。
* ヘッダー名が<code>api-key</code>ではなく<code>Authorization</code>など、誤ったものを指定している。
2. <strong>エンドポイントURLの誤り</strong>:
* <code>{your-resource-name}</code>(Azure OpenAIリソース名)のスペルミス。
* <code>{your-deployment-id}</code>(モデルのデプロイ名)のスペルミス、またはデプロイ自体が存在しない。
* Azure OpenAIリソースが存在するリージョンと、URLで指定しているリージョンが一致しない(通常、リソース名でリージョンは暗黙的に決まるが、カスタムドメインなどを利用している場合は注意)。
* <code>api-version</code>が古い、または存在しないバージョンを指定している。
3. <strong>ネットワークまたはプロキシの問題</strong>:
* 企業内ネットワークでプロキシ設定が正しく行われていない場合、そもそもAzure OpenAI Serviceに到達できない。
* ファイアウォールでAzure OpenAI Serviceのエンドポイントがブロックされている。</p>
<p><strong>対処</strong>:
1. <strong>APIキーの確認</strong>:
* Azureポータルで該当のAzure OpenAIリソースにアクセスし、「キーとエンドポイント」ブレードから正しいAPIキー(Key 1またはKey 2)をコピーし、スクリプトに設定されているキーと比較確認します。
* VBA/PowerShellスクリプトで<code>SetRequestHeader "api-key", YOUR_API_KEY</code>が正確に記述されているか確認します。
2. <strong>エンドポイントURLの確認</strong>:
* Azureポータルの「キーとエンドポイント」ブレードに表示される「エンドポイント」URLを正確にコピーし、スクリプト内の<code>strURL</code>や<code>$uri</code>と照合します。
* 特に<code>deployments/{your-deployment-id}</code>の部分の<code>your-deployment-id</code>が、実際にデプロイしたモデルに付けた「デプロイ名」と一致するか確認します。<code>model</code>名(例: <code>gpt-3.5-turbo</code>)を直接使うのではなく、デプロイ名を使います。
* <code>api-version</code>もポータルや公式ドキュメントで最新かつ推奨されているバージョンを確認して設定します。
3. <strong>ネットワーク/プロキシ設定の確認</strong>:
* VBAの<code>WinHttpRequest</code>では<code>SetProxy</code>メソッド、PowerShellの<code>Invoke-RestMethod</code>では<code>-Proxy</code>パラメータを適切に設定しているか確認します。
* 必要であれば、社内IT部門に相談し、Azure OpenAI Serviceへのアウトバウンド接続が許可されているか確認します。</p>
<p>これらの確認を徹底することで、認証とエンドポイントに関するエラーのほとんどは解決できます。</p>
<h2 class="wp-block-heading">まとめ</h2>
<p>VBAやPowerShellといった既存の業務自動化基盤からAzure OpenAI Serviceの力を引き出すことは、適切な知識と実装さえあれば十分に可能です。本稿では、<code>WinHttpRequest</code>と<code>Invoke-RestMethod</code>を用いたHTTPリクエストの基本から、JSONリクエスト/レスポンスの構築と解析、そしてAPIキー認証やエンドポイントの厳密な指定といった内部動作まで踏み込みました。</p>
<p>最小限の実装から始まり、エラーハンドリング、プロキシ設定、セキュリティを考慮したAPIキーの管理へと堅牢化する過程を通じて、実務で安心して使えるスクリプトの作成指針を示しました。また、ベンチマークの観点や、よくある失敗例とその対処法についても解説することで、あなたがAI連携の荒波を乗り越えるための羅針盤となることを目指しました。</p>
<p>この知識を基盤として、既存のVBA/PowerShell資産にAIの知見を注入し、業務効率化や新たな価値創造へと繋げていただければ幸いです。</p>
<h2 class="wp-block-heading">運用チェックリスト</h2>
<ul class="wp-block-list">
<li><strong>APIキーの管理とローテーション</strong>:
<ul>
<li>APIキーはコードに直接埋め込まず、環境変数やセキュアな設定ファイルで管理しているか?</li>
<li>定期的なAPIキーのローテーション手順は確立されているか?</li>
</ul></li>
<li><strong>エンドポイントURLの変更対応</strong>:
<ul>
<li>将来的なAPIバージョンアップやリソース移転に備え、URLは設定値として外部化されているか?</li>
</ul></li>
<li><strong>プロンプトエンジニアリングの継続的な改善</strong>:
<ul>
<li>生成されるAIの応答品質を向上させるため、プロンプトの設計は定期的に見直されているか?</li>
</ul></li>
<li><strong>レスポンスエラーハンドリングの徹底</strong>:
<ul>
<li>HTTPステータスコード(4xx, 5xx)だけでなく、APIから返されるエラーレスポンスボディも適切に解析・ログ出力されているか?</li>
<li>リトライロジックやフォールバック処理は考慮されているか?</li>
</ul></li>
<li><strong>課金状況の監視</strong>:
<ul>
<li>AzureポータルでAzure OpenAI Serviceの利用状況(トークン数、コスト)を定期的に確認し、予期せぬ高額請求を防ぐ体制があるか?</li>
</ul></li>
<li><strong>タイムアウト設定の適切性</strong>:
<ul>
<li>API呼び出しのタイムアウトは、業務要件とAPIの応答特性に合わせて適切に設定されているか?</li>
</ul></li>
<li><strong>プロキシ環境への対応</strong>:
<ul>
<li>企業内のプロキシサーバーを経由する場合、スクリプトはプロキシ設定に正しく対応しているか?</li>
</ul></li>
<li><strong>ログ出力の設計</strong>:
<ul>
<li>APIの呼び出し時刻、リクエスト内容(機密情報はマスク)、レスポンス、エラー情報が適切にログに出力され、トラブルシューティングに役立つか?</li>
</ul></li>
</ul>
<h2 class="wp-block-heading">参考リンク</h2>
<ul class="wp-block-list">
<li><a href="https://learn.microsoft.com/ja-jp/azure/cognitive-services/openai/">Azure OpenAI Service のドキュメント</a></li>
<li><a href="https://learn.microsoft.com/ja-jp/azure/cognitive-services/openai/reference#chat-completions">Azure OpenAI Service 用 Chat Completions API リファレンス</a></li>
</ul>
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
を使う「レイトバインディング」は参照設定不要ですが、わずかにパフォーマンスが劣ります。PtrSafe
やLongPtr
は、主に32bit/64bit環境でDLL関数を直接呼び出すDeclare
ステートメントで使用されるキーワードです。WinHttpRequest
自体はCOMオブジェクトとして抽象化されているため、直接的なPtrSafe
は不要ですが、VBAのLong
型は32bit整数であるため、64bit環境でポインタや非常に大きな数値を扱う場合はLongPtr
やLongLong
を使用する必要があります。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
ブロックでZeroFreeBSTR
とDispose
を呼び出すことで、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呼び出しコード
}
テスト観点
応答時間 (Latency) :
短いプロンプトと長いプロンプトでの応答時間を計測。
max_tokens
を小さく設定した場合と大きく設定した場合での変化。
ネットワーク状況(プロキシの有無、帯域)による変動。
エラーハンドリング :
無効なAPIキー、無効なデプロイID、不正なJSONボディを意図的に送信し、適切にエラーが捕捉され、情報がログに出力されるかを確認。
タイムアウト設定を超過するような長時間応答の場合、スクリプトがハングせずタイムアウトエラーを適切に処理するか。
トークン使用量 :
同じプロンプトでもtemperature
やtop_p
などのパラメータを変えた場合、生成されるトークン数にどの程度影響が出るか。
Azureポータルのメトリックで、実際にかかったトークン数が想定と一致するか確認。
プロンプトの堅牢性 :
特殊文字(JSONエスケープが必要な文字)を含むプロンプトが正しく処理されるか。
長すぎるプロンプトがモデルのコンテキストウィンドウを超過した場合、APIが適切にエラーを返すか(通常は400 Bad Request
)。
応用例/代替案
応用例
Excel VBA :
既存のデータ分析シートにAI要約機能を追加。例えば、顧客コメントのセル範囲を選択し、要約ボタンを押すと別セルに要約結果が出力される。
定型的な報告書の下書き生成。
多言語対応(翻訳)。
PowerShell :
ログファイルから異常検知の要約を生成し、レポートとして出力。
Active Directoryユーザー情報からプロファイル記述を自動生成。
インシデント管理システムへの自動チケット起票スクリプトに、AIによる事象分析を追加。
代替案
Azure Functions / Logic Apps :
VBA/PowerShellスクリプトが直接Azure OpenAI APIを叩くのではなく、Azure FunctionsやLogic Appsを中継役として挟む。
利点: APIキーの管理を一元化し、ロジックをクラウド側で集中管理できる。VBA/PowerShell側はよりシンプルなHTTP呼び出しになる。レートリミット管理なども容易。
欠点: 追加のAzureリソースと費用、開発・デプロイの手間。
Python等モダン言語の活用 :
OpenAI公式のPythonライブラリやrequestsのようなHTTPクライアントライブラリを利用すれば、JSONの構築やパースがより容易で堅牢。
VBA/PowerShellからはPythonスクリプトを呼び出す形にする。
利点: 開発効率向上、豊富なエコシステム。
欠点: 実行環境にPythonのインストールが必要、VBA/PowerShellとの連携オーバーヘッド。
ローカル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の力を引き出すことは、適切な知識と実装さえあれば十分に可能です。本稿では、WinHttpRequest
とInvoke-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の呼び出し時刻、リクエスト内容(機密情報はマスク)、レスポンス、エラー情報が適切にログに出力され、トラブルシューティングに役立つか?
参考リンク
コメント