<p>VBA/PowerShellからAzure OpenAI API連携:内部挙動と堅牢化の極意</p>
<h2 class="wp-block-heading">導入(問題設定)</h2>
<p>Excelマクロやバッチ処理といった業務自動化の現場では、VBAやPowerShellが今なお広く利用されています。これらのスクリプト言語に、最先端のAzure OpenAI Serviceの能力を組み込みたいというニーズは日に日に高まっています。しかし、単にAPIを呼び出す表層的なHowToでは、実務で直面するであろう「なぜか動かない」「本番環境で落ちる」「性能が出ない」といった問題に対処できません。</p>
<p>本記事では、VBAおよびPowerShellという、時にレガシーとも言われる環境から、Azure OpenAI ServiceのREST APIを連携させる方法を、その内部動作、境界条件、潜在的な落とし穴まで深掘りして解説します。最小限の実装から始め、最終的には堅牢なシステムを構築するための知見とコードを提供します。外部ライブラリに極力依存せず、素の機能でどこまでできるかを追求することで、本質的な理解を促します。</p>
<h2 class="wp-block-heading">理論の要点</h2>
<p>Azure OpenAI Serviceは、HTTP/HTTPSプロトコルを介したRESTful APIとして提供されます。クライアント(VBA/PowerShell)は、特定のURIに対してHTTPメソッド(主にPOST)を用いてリクエストを送信し、JSON形式のレスポンスを受け取ります。この一連の流れを理解することが、堅牢な連携の第一歩です。</p>
<h3 class="wp-block-heading">1. エンドポイントとAPIバージョン</h3>
<p>Azure OpenAI Serviceのエンドポイントは、一般的なOpenAI APIとは構造が異なります。
<code>https://<your-resource-name>.openai.azure.com/openai/deployments/<your-deployment-name>/chat/completions?api-version=<api-version></code>
ここで重要なのは、<code>api-version</code>クエリパラメータの存在です。これを忘れると認証エラーや無効なリクエストとして弾かれることがあります。</p>
<h3 class="wp-block-heading">2. 認証方式</h3>
<p>Azure OpenAI Serviceでは、APIキーによる認証が主流です。APIキーはHTTPヘッダーに<code>api-key: YOUR_API_KEY</code>として含める必要があります。通常のOpenAI APIで使われる<code>Authorization: Bearer YOUR_API_KEY</code>とは異なる点に注意が必要です。</p>
<h3 class="wp-block-heading">3. リクエストとレスポンスのJSON構造</h3>
<h4 class="wp-block-heading">a. リクエストボディ</h4>
<p>Chat Completions API(<code>gpt-3.5-turbo</code>, <code>gpt-4</code>など)では、リクエストボディは以下のようなJSON構造をとります。<code>messages</code>配列に会話履歴をオブジェクトとして渡すのが特徴です。</p>
<pre data-enlighter-language="generic">{
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Tell me a joke."}
],
"max_tokens": 100,
"temperature": 0.7
}
</pre>
<ul class="wp-block-list">
<li><strong><code>role</code></strong>: <code>system</code>, <code>user</code>, <code>assistant</code>のいずれか。<code>system</code>はAIの振る舞いを定義、<code>user</code>はユーザーの入力、<code>assistant</code>はAIの応答を表します。</li>
<li><strong><code>content</code></strong>: メッセージ本体。</li>
<li><strong><code>max_tokens</code></strong>: 生成されるテキストの最大トークン数。</li>
<li><strong><code>temperature</code></strong>: 生成されるテキストのランダム性(創造性)を制御します。0.0から2.0の範囲で、高いほど多様な応答が生成されやすくなります。</li>
</ul>
<h4 class="wp-block-heading">b. レスポンスボディ</h4>
<p>成功すると、以下のようなJSONが返されます。生成されたテキストは<code>choices[0].message.content</code>に格納されます。</p>
<pre data-enlighter-language="generic">{
"id": "chatcmpl-...",
"object": "chat.completion",
"created": 1677652288,
"model": "gpt-35-turbo",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Why don't scientists trust atoms? Because they make up everything!"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 17,
"completion_tokens": 16,
"total_tokens": 33
}
}
</pre>
<h3 class="wp-block-heading">4. VBAにおけるCOMオブジェクトと64bit/32bitの差異</h3>
<p>VBAからHTTP通信を行うには、通常、<code>Microsoft WinHttpRequest Services</code>(<code>WinHttpRequest</code>)や<code>Microsoft XML, v6.0</code>(<code>MSXML2.XMLHTTP60</code>)といったCOMオブジェクトを利用します。これらはVBAの実行環境(32bit/64bit)によらず安定して動作します。</p>
<p>一方、<code>Declare</code>ステートメントを使ってWindows API(WinAPI)を直接呼び出す場合、32bitと64bit環境でポインタのサイズが異なるため注意が必要です。64bit環境ではポインタが8バイトになるため、VBAでは<code>LongPtr</code>型を使用し、<code>Declare</code>ステートメントには<code>PtrSafe</code>キーワードを付加する必要があります。
本記事で扱うHTTP通信においてはCOMオブジェクトを使うため直接<code>PtrSafe</code>/<code>LongPtr</code>の記述は必要ありませんが、VBAでより低レベルな操作を行う際には不可欠な知識です。</p>
<h3 class="wp-block-heading">Azure OpenAI Chat Completions API 主要仕様</h3>
<ul class="wp-block-list">
<li><strong>エンドポイントURL構造</strong>:
<ul>
<li><code>https://<your-resource-name>.openai.azure.com/openai/deployments/<your-deployment-name>/chat/completions?api-version=YYYY-MM-DD</code></li>
</ul></li>
<li><strong>HTTPメソッド</strong>:
<ul>
<li><code>POST</code></li>
</ul></li>
<li><strong>HTTPヘッダー</strong>:
<ul>
<li><code>Content-Type</code>: <code>application/json</code> (必須)</li>
<li><code>api-key</code>: <code><your-api-key></code> (必須、Azure OpenAI専用認証)</li>
</ul></li>
<li><strong>リクエストボディ (JSON) 主要フィールド</strong>:
<ul>
<li><code>messages</code>: <code>array</code> of <code>object</code> (必須)
<ul>
<li><code>role</code>: <code>string</code> (<code>system</code>, <code>user</code>, <code>assistant</code>)</li>
<li><code>content</code>: <code>string</code></li>
</ul></li>
<li><code>max_tokens</code>: <code>integer</code> (オプション, 生成トークン上限)</li>
<li><code>temperature</code>: <code>number</code> (オプション, 0.0-2.0, 創造性)</li>
<li><code>top_p</code>: <code>number</code> (オプション, 0.0-1.0, 候補トークンの選択範囲)</li>
<li><code>stop</code>: <code>string</code> or <code>array</code> of <code>string</code> (オプション, 停止シーケンス)</li>
</ul></li>
<li><strong>レスポンスボディ (JSON) 主要フィールド</strong>:
<ul>
<li><code>id</code>: <code>string</code> (リクエストID)</li>
<li><code>object</code>: <code>string</code> (<code>chat.completion</code>)</li>
<li><code>choices</code>: <code>array</code> of <code>object</code>
<ul>
<li><code>message</code>: <code>object</code>
<ul>
<li><code>role</code>: <code>string</code> (<code>assistant</code>)</li>
<li><code>content</code>: <code>string</code> (生成されたテキスト)</li>
</ul></li>
<li><code>finish_reason</code>: <code>string</code> (<code>stop</code>, <code>length</code>など)</li>
</ul></li>
<li><code>usage</code>: <code>object</code>
<ul>
<li><code>prompt_tokens</code>: <code>integer</code> (プロンプトトークン数)</li>
<li><code>completion_tokens</code>: <code>integer</code> (生成トークン数)</li>
<li><code>total_tokens</code>: <code>integer</code> (合計トークン数)</li>
</ul></li>
</ul></li>
</ul>
<h2 class="wp-block-heading">実装(最小→堅牢化)</h2>
<h3 class="wp-block-heading">共通設定</h3>
<p>以下の変数は、VBA/PowerShellともに自身の環境に合わせて設定してください。</p>
<pre data-enlighter-language="generic">' Azure OpenAI Serviceの設定
Const AZURE_OPENAI_RESOURCE_NAME As String = "YOUR_AZURE_OPENAI_RESOURCE_NAME" ' 例: my-openai-resource
Const AZURE_OPENAI_DEPLOYMENT_NAME As String = "YOUR_AZURE_OPENAI_DEPLOYMENT_NAME" ' 例: gpt-35-turbo
Const AZURE_OPENAI_API_KEY As String = "YOUR_AZURE_OPENAI_API_KEY"
Const AZURE_OPENAI_API_VERSION As String = "2023-05-15" ' 現在の推奨バージョン
</pre>
<pre data-enlighter-language="generic"># Azure OpenAI Serviceの設定
$AzureOpenAIResourceName = "YOUR_AZURE_OPENAI_RESOURCE_NAME" # 例: my-openai-resource
$AzureOpenAIDeploymentName = "YOUR_AZURE_OPENAI_DEPLOYMENT_NAME" # 例: gpt-35-turbo
$AzureOpenAIApiKey = "YOUR_AZURE_OPENAI_API_KEY"
$AzureOpenAIApiVersion = "2023-05-15" # 現在の推奨バージョン
</pre>
<h3 class="wp-block-heading">VBAでの実装</h3>
<h4 class="wp-block-heading">最小実装</h4>
<p><code>WinHttpRequest</code>オブジェクトを使用して、シンプルなテキスト生成を行います。JSONの構築とパースは、最も基本的な文字列操作で行います。</p>
<pre data-enlighter-language="generic">Attribute VB_Name = "Module1"
Option Explicit
Private Const AZURE_OPENAI_RESOURCE_NAME As String = "YOUR_AZURE_OPENAI_RESOURCE_NAME"
Private Const AZURE_OPENAI_DEPLOYMENT_NAME As String = "YOUR_AZURE_OPENAI_DEPLOYMENT_NAME"
Private Const AZURE_OPENAI_API_KEY As String = "YOUR_AZURE_OPENAI_API_KEY"
Private Const AZURE_OPENAI_API_VERSION As String = "2023-05-15"
Public Function CallAzureOpenAI(ByVal prompt As String) As String
Dim httpRequest As Object
Dim url As String
Dim requestBody As String
Dim responseText As String
Dim startPos As Long, endPos As Long
' URLの構築
url = "https://" & AZURE_OPENAI_RESOURCE_NAME & ".openai.azure.com/openai/deployments/" & _
AZURE_OPENAI_DEPLOYMENT_NAME & "/chat/completions?api-version=" & AZURE_OPENAI_API_VERSION
' リクエストボディの構築 (最小限のJSON文字列連結)
requestBody = "{""messages"": [{""role"": ""user"", ""content"": """ & Replace(prompt, """", """""") & """}], ""max_tokens"": 150, ""temperature"": 0.7}"
Set httpRequest = CreateObject("WinHttp.WinHttpRequest.5.1")
With httpRequest
.Open "POST", url, False ' 同期通信
.SetRequestHeader "Content-Type", "application/json"
.SetRequestHeader "api-key", AZURE_OPENAI_API_KEY
.Send requestBody
responseText = .ResponseText
End With
' レスポンスの簡易パース (contentだけを抜き出す)
' 厳密なJSONパースではないため、JSON構造が変わると破綻します。
startPos = InStr(responseText, """content"":""")
If startPos > 0 Then
startPos = startPos + Len("""content"":""")
' "が来るまで読み飛ばす
startPos = InStr(startPos, responseText, """")
If startPos > 0 Then
startPos = startPos + 1 ' 開始の " の次から
endPos = InStr(startPos, responseText, """") ' 次の " まで
If endPos > 0 Then
CallAzureOpenAI = Replace(Mid(responseText, startPos, endPos - startPos), "\n", vbCrLf)
Else
CallAzureOpenAI = "Error: content end quote not found."
End If
Else
CallAzureOpenAI = "Error: content start quote not found."
End If
Else
CallAzureOpenAI = "Error: 'content' field not found. Full response: " & responseText
End If
Set httpRequest = Nothing
End Function
' 実行例
Public Sub TestAzureOpenAICall()
Dim result As String
result = CallAzureOpenAI("今日の天気は?")
Debug.Print "AIの応答: " & result
End Sub
</pre>
<h4 class="wp-block-heading">堅牢化実装</h4>
<p>エラーハンドリング、タイムアウト設定、リトライ処理、より安全なJSONパース(正規表現を利用)、そしてVBAにおける64bit環境への考慮について説明します。</p>
<pre data-enlighter-language="generic">Attribute VB_Name = "Module1"
Option Explicit
Private Const AZURE_OPENAI_RESOURCE_NAME As String = "YOUR_AZURE_OPENAI_RESOURCE_NAME"
Private Const AZURE_OPENAI_DEPLOYMENT_NAME As String = "YOUR_AZURE_OPENAI_DEPLOYMENT_NAME"
Private Const AZURE_OPENAI_API_KEY As String = "YOUR_AZURE_OPENAI_API_KEY"
Private Const AZURE_OPENAI_API_VERSION As String = "2023-05-15"
' 堅牢化のための定数
Private Const MAX_RETRIES As Long = 3
Private Const RETRY_DELAY_SECONDS As Long = 5 ' 秒
Private Const REQUEST_TIMEOUT_SECONDS As Long = 60 ' 秒
' PtrSafeとLongPtrについて(WinAPIを直接呼び出す場合の考慮点)
' WinHttpRequestはCOMオブジェクトのため、直接PtrSafe/LongPtrは不要ですが、
' 広くVBAで外部DLL/APIを扱う場合の知識として明記します。
' #If VBA7 Then
' Private Declare PtrSafe Function GetPrivateProfileString Lib "kernel32" _
' Alias "GetPrivateProfileStringA" (ByVal lpAppName As String, _
' ByVal lpKeyName As String, ByVal lpDefault As String, _
' ByVal lpReturnedString As String, ByVal nSize As Long, _
' ByVal lpFileName As String) As Long
' #Else
' Private Declare Function GetPrivateProfileString Lib "kernel32" _
' Alias "GetPrivateProfileStringA" (ByVal lpAppName As String, _
' ByVal lpKeyName As String, ByVal lpDefault As String, _
' ByVal lpReturnedString As String, ByVal nSize As Long, _
' ByVal lpFileName As String) As Long
' #End If
' 上記のように、VBA7 (Office 2010以降の64bit版VBA) ではPtrSafeとLongPtrが必須となります。
' LongPtrはポインタを格納するための型で、32bit環境ではLong (4バイト)、64bit環境ではLongLong (8バイト) になります。
Public Function CallAzureOpenAI_Robust(ByVal prompt As String, Optional ByVal model As String = "gpt-35-turbo", Optional ByVal temperature As Single = 0.7) As String
Dim httpRequest As Object
Dim url As String
Dim requestBody As String
Dim responseText As String
Dim retryCount As Long
Dim regex As Object ' VBScript.RegExp
Dim matches As Object ' MatchCollection
Dim match As Object ' Match
Dim result As String
result = ""
' URLの構築
url = "https://" & AZURE_OPENAI_RESOURCE_NAME & ".openai.azure.com/openai/deployments/" & _
AZURE_OPENAI_DEPLOYMENT_NAME & "/chat/completions?api-version=" & AZURE_OPENAI_API_VERSION
' リクエストボディの構築関数
requestBody = BuildChatCompletionRequestBody(prompt, model, temperature)
Set httpRequest = CreateObject("WinHttp.WinHttpRequest.5.1")
With httpRequest
.SetRequestHeader "Content-Type", "application/json"
.SetRequestHeader "api-key", AZURE_OPENAI_API_KEY
.SetTimeouts REQUEST_TIMEOUT_SECONDS * 1000, REQUEST_TIMEOUT_SECONDS * 1000, REQUEST_TIMEOUT_SECONDS * 1000, REQUEST_TIMEOUT_SECONDS * 1000 ' Resolve, Connect, Send, Receive
For retryCount = 0 To MAX_RETRIES - 1
On Error GoTo ErrorHandler
.Open "POST", url, False ' 同期通信
.Send requestBody
' HTTPステータスコードを確認
If .Status >= 200 And .Status < 300 Then
responseText = .ResponseText
Exit For ' 成功したらループを抜ける
ElseIf .Status = 429 Or .Status >= 500 Then ' レートリミット (429) またはサーバーエラー (5xx)
Debug.Print "HTTP Error: " & .Status & " - " & .StatusText & ". Retrying in " & RETRY_DELAY_SECONDS & " seconds..."
Application.Wait Now + TimeValue("00:00:" & RETRY_DELAY_SECONDS)
If retryCount = MAX_RETRIES - 1 Then
Err.Raise 9999, "CallAzureOpenAI_Robust", "Max retries reached. Last HTTP Error: " & .Status & " - " & .StatusText
End If
Else ' その他のエラー
Err.Raise 9998, "CallAzureOpenAI_Robust", "HTTP Error: " & .Status & " - " & .StatusText & vbCrLf & "Response: " & .ResponseText
End If
ErrorHandler:
Debug.Print "Runtime Error: " & Err.Description & ". Retrying in " & RETRY_DELAY_SECONDS & " seconds..."
Application.Wait Now + TimeValue("00:00:" & RETRY_DELAY_SECONDS)
If retryCount = MAX_RETRIES - 1 Then
Err.Raise Err.Number, Err.Source, "Max retries reached. Last Error: " & Err.Description
End If
Resume Next ' エラー発生箇所から処理を再開
Next retryCount
End With
' レスポンスの堅牢なパース (VBScript.RegExpを利用)
Set regex = CreateObject("VBScript.RegExp")
With regex
.Pattern = """content""\s*:\s*""([^""]*)""" ' contentフィールドの値をキャプチャ
.Global = False ' 最初の一致のみ
.IgnoreCase = False
End With
If regex.Test(responseText) Then
Set matches = regex.Execute(responseText)
If matches.Count > 0 Then
' キャプチャグループ1がcontentの値
result = Replace(matches(0).SubMatches(0), "\n", vbCrLf) ' \nを改行に変換
CallAzureOpenAI_Robust = result
Else
CallAzureOpenAI_Robust = "Error: content not found in regex match. Raw response: " & responseText
End If
Else
CallAzureOpenAI_Robust = "Error: 'content' field not found in response using regex. Raw response: " & responseText
End If
Set httpRequest = Nothing
Set regex = Nothing
Set matches = Nothing
Set match = Nothing
Exit Function
' グローバルなエラーハンドラ(ループを抜けた後の最終的なエラー処理)
FinalErrorHandler:
CallAzureOpenAI_Robust = "Fatal Error: " & Err.Description & ". Last response: " & responseText
Set httpRequest = Nothing
Set regex = Nothing
Set matches = Nothing
Set match = Nothing
End Function
' リクエストボディを生成するヘルパー関数
Private Function BuildChatCompletionRequestBody(ByVal prompt As String, ByVal model As String, ByVal temperature As Single) As String
Dim jsonBuilder As String
jsonBuilder = "{"
jsonBuilder = jsonBuilder & """messages"": ["
jsonBuilder = jsonBuilder & "{""role"": ""user"", ""content"": """ & EscapeJsonString(prompt) & """}"
jsonBuilder = jsonBuilder & "],"
jsonBuilder = jsonBuilder & """max_tokens"": 150," ' 固定値としておく
jsonBuilder = jsonBuilder & """temperature"": " & Replace(CStr(temperature), ",", ".") & "" ' 小数点対応
jsonBuilder = jsonBuilder & "}"
BuildChatCompletionRequestBody = jsonBuilder
End Function
' JSON文字列のエスケープ処理
Private Function EscapeJsonString(ByVal inputString As String) As String
Dim escapedString As String
escapedString = Replace(inputString, "\", "\\")
escapedString = Replace(escapedString, """", "\""")
escapedString = Replace(escapedString, vbCrLf, "\n")
escapedString = Replace(escapedString, vbCr, "\r")
escapedString = Replace(escapedString, vbLf, "\n")
' 他にもエスケープが必要な文字があれば追加
EscapeJsonString = escapedString
End Function
' 実行例
Public Sub TestAzureOpenAICall_Robust()
Dim result As String
On Error GoTo TestError
result = CallAzureOpenAI_Robust("宇宙の果てはどうなっている?", "gpt-4", 0.9)
Debug.Print "AIの応答 (堅牢版): " & result
Exit Sub
TestError:
Debug.Print "テスト中にエラーが発生しました: " & Err.Description
End Sub
</pre>
<h3 class="wp-block-heading">PowerShellでの実装</h3>
<h4 class="wp-block-heading">最小実装</h4>
<p><code>Invoke-RestMethod</code>コマンドレットは、JSONのリクエスト/レスポンスの処理を大幅に簡略化してくれます。</p>
<pre data-enlighter-language="generic"># 最小実装
function Call-AzureOpenAI {
param (
[string]$Prompt
)
$AzureOpenAIResourceName = "YOUR_AZURE_OPENAI_RESOURCE_NAME"
$AzureOpenAIDeploymentName = "YOUR_AZURE_OPENAI_DEPLOYMENT_NAME"
$AzureOpenAIApiKey = "YOUR_AZURE_OPENAI_API_KEY"
$AzureOpenAIApiVersion = "2023-05-15"
$url = "https://$AzureOpenAIResourceName.openai.azure.com/openai/deployments/$AzureOpenAIDeploymentName/chat/completions?api-version=$AzureOpenAIApiVersion"
$headers = @{
"Content-Type" = "application/json"
"api-key" = $AzureOpenAIApiKey
}
$body = @{
messages = @(
@{ role = "user"; content = $Prompt }
)
max_tokens = 150
temperature = 0.7
} | ConvertTo-Json -Depth 4 # -Depthでネストされたオブジェクトも適切に変換
try {
$response = Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body $body -ContentType "application/json"
# PowerShellはレスポンスのJSONを自動的にオブジェクトに変換してくれる
$response.choices[0].message.content
}
catch {
Write-Error "Azure OpenAI API呼び出し中にエラーが発生しました: $($_.Exception.Message)"
$null
}
}
# 実行例
$result = Call-AzureOpenAI -Prompt "PowerShellでAzure OpenAIを使うメリットは何ですか?"
if ($result) {
Write-Host "AIの応答: $result"
}
</pre>
<h4 class="wp-block-heading">堅牢化実装</h4>
<p>エラーハンドリング、リトライ戦略、タイムアウト設定、そしてAPIキーのセキュアな管理に焦点を当てます。</p>
<pre data-enlighter-language="generic"># 堅牢化実装
function Call-AzureOpenAI-Robust {
param (
[string]$Prompt,
[string]$Model = "gpt-35-turbo",
[float]$Temperature = 0.7,
[int]$MaxRetries = 3,
[int]$RetryDelaySeconds = 5, # 秒
[int]$RequestTimeoutSeconds = 60 # 秒
)
$AzureOpenAIResourceName = "YOUR_AZURE_OPENAI_RESOURCE_NAME"
$AzureOpenAIDeploymentName = "YOUR_AZURE_OPENAI_DEPLOYMENT_NAME"
$AzureOpenAIApiVersion = "2023-05-15"
# APIキーの安全な管理: 環境変数から取得することを推奨
# $AzureOpenAIApiKey = $env:AZURE_OPENAI_API_KEY
# 環境変数に設定できない場合、Credentialオブジェクトを使用するなどの工夫が必要
# 例: $AzureOpenAIApiKey = (Get-Credential -UserName "azure-openai" -Message "Azure OpenAI API Key").Password
# ここではテストのため直接定義しますが、本番環境では必ず安全な方法を選んでください。
$AzureOpenAIApiKey = "YOUR_AZURE_OPENAI_API_KEY"
$url = "https://$AzureOpenAIResourceName.openai.azure.com/openai/deployments/$AzureOpenAIDeploymentName/chat/completions?api-version=$AzureOpenAIApiVersion"
$headers = @{
"Content-Type" = "application/json"
"api-key" = $AzureOpenAIApiKey
}
$body = @{
messages = @(
@{ role = "user"; content = $Prompt }
)
model = $Model
max_tokens = 150
temperature = $Temperature
} | ConvertTo-Json -Depth 4 # -Depthでネストされたオブジェクトも適切に変換
$attempt = 0
while ($attempt -lt $MaxRetries) {
try {
$response = Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body $body -ContentType "application/json" -TimeoutSec $RequestTimeoutSeconds -ErrorAction Stop
return $response.choices[0].message.content
}
catch {
$attempt++
Write-Warning "API呼び出し失敗 (試行 $attempt/$MaxRetries): $($_.Exception.Message)"
# HTTPステータスコードによるリトライ条件の判断
if ($_.Exception.Response) {
$statusCode = [int]$_.Exception.Response.StatusCode
$responseBody = (New-Object IO.StreamReader($_.Exception.Response.GetResponseStream())).ReadToEnd()
Write-Warning "HTTP Status Code: $statusCode, Response Body: $responseBody"
# 429 (Too Many Requests) または 5xx (Server Error) の場合にリトライ
if ($statusCode -eq 429 -or $statusCode -ge 500) {
if ($attempt -lt $MaxRetries) {
Write-Host "リトライのため $($RetryDelaySeconds) 秒待機します..."
Start-Sleep -Seconds $RetryDelaySeconds
}
}
else {
# その他のエラーはリトライせず終了
Write-Error "致命的なHTTPエラーが発生しました。リトライしません。"
return $null
}
}
else {
# ネットワークエラーなど、Responseオブジェクトがない場合もリトライ
if ($attempt -lt $MaxRetries) {
Write-Host "ネットワークエラーまたは不明なエラー。リトライのため $($RetryDelaySeconds) 秒待機します..."
Start-Sleep -Seconds $RetryDelaySeconds
}
}
}
}
Write-Error "最大リトライ回数に達しました。Azure OpenAI API呼び出しに失敗しました。"
return $null
}
# 実行例
$resultRobust = Call-AzureOpenAI-Robust -Prompt "猫が魚を捕まえる面白い話を作ってください。" -Model "gpt-4" -Temperature 0.9
if ($resultRobust) {
Write-Host "AIの応答 (堅牢版): $resultRobust"
}
</pre>
<h2 class="wp-block-heading">ベンチ/検証</h2>
<p>実務で利用する上で、API連携の性能と信頼性を評価することは不可欠です。</p>
<h3 class="wp-block-heading">計測方法</h3>
<ul class="wp-block-list">
<li><strong>応答速度</strong>: 各API呼び出しの開始からレスポンス受信までの時間を計測します。複数回実行し、平均値、最小値、最大値を記録します。</li>
<li><strong>成功/失敗率</strong>: 指定期間内でのAPI呼び出し総数と、成功した呼び出し数(HTTP 2xx)、失敗した呼び出し数(HTTP 4xx, 5xx、ネットワークエラー、タイムアウト)を記録します。</li>
<li><strong>トークン使用量</strong>: レスポンスボディの<code>usage</code>フィールドから、<code>prompt_tokens</code>、<code>completion_tokens</code>、<code>total_tokens</code>を取得し、コスト見積もりに利用します。</li>
</ul>
<h3 class="wp-block-heading">テスト観点</h3>
<ul class="wp-block-list">
<li><strong>プロンプトの長さと複雑性</strong>: 短いプロンプト、長いプロンプト、複雑な指示を含むプロンプトで応答速度や生成品質を比較します。</li>
<li><strong>同時実行数(スループット)</strong>: 複数のVBA/PowerShellプロセスやスレッドから同時にAPIを呼び出し、Azure OpenAI Serviceのレートリミットやスケーラビリティの影響を確認します。特にVBAではシングルスレッドなので、複数Excelインスタンス起動などの工夫が必要になります。</li>
<li><strong>エラーレスポンスの種類</strong>:
<ul>
<li><strong>APIキー不正/デプロイ名不一致 (401/404)</strong>: 意図的に不正なキーやデプロイ名を指定し、適切なエラーハンドリングが行われるか確認。</li>
<li><strong>レートリミット (429)</strong>: 短時間に大量のリクエストを送信し、リトライロジックが機能するか確認。</li>
<li><strong>無効なプロンプト/パラメーター (400)</strong>: 不適切なJSONや無効な<code>temperature</code>値などを送り、エラー応答を検証。</li>
<li><strong>ネットワーク遅延/タイムアウト</strong>: 意図的にネットワークを切断したり、極端に短いタイムアウトを設定し、処理が中断されずに適切にエラー処理されるか確認。</li>
</ul></li>
<li><strong>64bit/32bit環境での動作確認</strong>: VBAの場合、異なるOffice環境で<code>WinHttpRequest</code>などのCOMオブジェクトが正しくロード・動作するかを確認します。特に<code>PtrSafe</code>や<code>LongPtr</code>が関係するWinAPI呼び出しを含む場合は、より詳細なテストが必要です。</li>
</ul>
<h2 class="wp-block-heading">応用例/代替案</h2>
<h3 class="wp-block-heading">応用例</h3>
<ul class="wp-block-list">
<li><strong>Excelシートの自動要約/翻訳</strong>: Excelの特定列に入力されたテキストをAIが要約・翻訳し、別の列に結果を書き出す。報告書作成支援や多言語対応に活用。</li>
<li><strong>定型業務レポート作成支援</strong>: 複数のデータソースから情報を集約し、AIが自然言語でレポートの草稿を生成。数値データに基づいた洞察のテキスト化。</li>
<li><strong>メールからの情報抽出と分類</strong>: 受信メールの内容をAIが解析し、重要な情報(日時、担当者、アクションアイテム)を抽出し、Excelや他のシステムに連携する。</li>
<li><strong>チャットボットのプロトタイピング</strong>: Excelシートを知識ベースとして、VBAマクロでユーザーからの問い合わせに応答する簡易チャットボット。</li>
</ul>
<h3 class="wp-block-heading">代替案</h3>
<p>VBA/PowerShellでの直接連携には限界や制約も存在します。より大規模なシステムや高い信頼性が求められる場合は、以下の代替案も検討してください。</p>
<ul class="wp-block-list">
<li><strong>Pythonによる連携</strong>:
<ul>
<li><strong>メリット</strong>: <code>requests</code>ライブラリによるHTTP通信の容易さ、<code>openai</code>公式SDKの存在、豊富なJSON処理ライブラリ、Pythonコミュニティによる活発な開発。データサイエンス領域との親和性も高く、高度な前処理・後処理が容易。</li>
<li><strong>デメリット</strong>: 実行環境の構築が必要、Excel/Windowsバッチとの連携にはラッパーやプロセスの起動などの工夫が必要。</li>
</ul></li>
<li><strong>Azure FunctionsやLogic Appsの活用</strong>:
<ul>
<li><strong>メリット</strong>: サーバーレスでスケーラブル、APIキーなどの機密情報を安全に管理できる(Key Vault連携)、HTTPトリガーやタイマートリガーなど柔軟な連携が可能。処理をクラウドにオフロードし、クライアント側の負荷を軽減。</li>
<li><strong>デメリット</strong>: クラウドサービスの構築・運用知識が必要、コスト管理が必要。</li>
</ul></li>
<li><strong>Power Automate / Power Appsとの連携</strong>:
<ul>
<li><strong>メリット</strong>: ローコード/ノーコードでAzure OpenAI Serviceと連携可能。既存のMicrosoft 365サービス(SharePoint, Teams, Outlook)との統合が容易。</li>
<li><strong>デメリット</strong>: 細かい制御が難しい場合がある、ライセンス体系による制約。</li>
</ul></li>
</ul>
<h2 class="wp-block-heading">まとめ</h2>
<p>本記事では、VBAおよびPowerShellからAzure OpenAI ServiceのREST APIを連携させる具体的な方法を、最小実装から堅牢化、そしてその内部動作まで深掘りして解説しました。VBAにおける<code>WinHttpRequest</code>と文字列操作、PowerShellにおける<code>Invoke-RestMethod</code>と<code>ConvertTo-Json</code>は、それぞれ異なるアプローチでREST APIとの橋渡し役を果たします。</p>
<p>特に、64bit環境における<code>PtrSafe</code>や<code>LongPtr</code>の概念、APIキーの適切な管理、HTTPステータスコードに基づいたリトライ処理、そして正規表現を用いたJSONの堅牢なパースは、実務で安定したシステムを構築する上で不可欠な要素です。</p>
<p>これらの知見とコードを活用することで、あなたのExcelマクロやPowerShellスクリプトが、Azure OpenAI Serviceの強力なAI能力を手に入れ、より高度な業務自動化を実現できるでしょう。</p>
<h3 class="wp-block-heading">運用チェックリスト</h3>
<ul class="wp-block-list">
<li>[ ] APIキーは安全な方法(環境変数、Azure Key Vaultなど)で管理されているか?(コードに直接記述されていないか?)</li>
<li>[ ] エンドポイントURLとAPIバージョンは最新かつ正確に設定されているか?</li>
<li>[ ] HTTPヘッダー(特に<code>api-key</code>と<code>Content-Type</code>)は正しく設定されているか?</li>
<li>[ ] リクエストボディのJSONは仕様通りに構築され、エスケープ処理は適切に行われているか?</li>
<li>[ ] HTTPステータスコードをチェックし、2xx系以外の場合に適切なエラーハンドリングが行われているか?</li>
<li>[ ] レートリミット(HTTP 429)やサーバーエラー(HTTP 5xx)に対するリトライ処理(指数バックオフなど)は実装されているか?</li>
<li>[ ] ネットワーク遅延やサービス側の応答遅延に備え、適切なタイムアウト設定が行われているか?</li>
<li>[ ] レスポンスボディのJSONパースは堅牢に行われ、エラー応答や予期せぬJSON構造にも対応できるか?</li>
<li>[ ] トークン使用量を監視し、コスト超過を防ぐ仕組みは検討されているか?</li>
<li>[ ] プロンプトインジェクションなど、意図しないAIの挙動を引き起こすプロンプトに対する対策は検討されているか?</li>
<li>[ ] ロギングは適切に実装されており、API呼び出しの成功/失敗、応答速度、エラー詳細が記録されているか?</li>
<li>[ ] モデルのバージョンアップやAPI仕様変更があった際に、コードを柔軟に更新できる設計になっているか?</li>
<li>[ ] VBAの場合、32bit/64bit環境で差異なく動作することが確認されているか?</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/overview">Azure OpenAI Service のドキュメント</a></li>
<li><a href="https://learn.microsoft.com/ja-jp/azure/cognitive-services/openai/reference">Azure OpenAI Service REST API のリファレンス</a></li>
</ul>
<h2 class="wp-block-heading">Mermaid 図</h2>
<div class="wp-block-merpress-mermaidjs diagram-source-mermaid"><pre class="mermaid">
sequenceDiagram
participant Client
participant Server
Client ->> Server: リクエスト
Server -->> Client: レスポンス
</pre></div>
VBA/PowerShellからAzure OpenAI API連携:内部挙動と堅牢化の極意
導入(問題設定)
Excelマクロやバッチ処理といった業務自動化の現場では、VBAやPowerShellが今なお広く利用されています。これらのスクリプト言語に、最先端のAzure OpenAI Serviceの能力を組み込みたいというニーズは日に日に高まっています。しかし、単にAPIを呼び出す表層的なHowToでは、実務で直面するであろう「なぜか動かない」「本番環境で落ちる」「性能が出ない」といった問題に対処できません。
本記事では、VBAおよびPowerShellという、時にレガシーとも言われる環境から、Azure OpenAI ServiceのREST APIを連携させる方法を、その内部動作、境界条件、潜在的な落とし穴まで深掘りして解説します。最小限の実装から始め、最終的には堅牢なシステムを構築するための知見とコードを提供します。外部ライブラリに極力依存せず、素の機能でどこまでできるかを追求することで、本質的な理解を促します。
理論の要点
Azure OpenAI Serviceは、HTTP/HTTPSプロトコルを介したRESTful APIとして提供されます。クライアント(VBA/PowerShell)は、特定のURIに対してHTTPメソッド(主にPOST)を用いてリクエストを送信し、JSON形式のレスポンスを受け取ります。この一連の流れを理解することが、堅牢な連携の第一歩です。
1. エンドポイントとAPIバージョン
Azure OpenAI Serviceのエンドポイントは、一般的なOpenAI APIとは構造が異なります。
https://<your-resource-name>.openai.azure.com/openai/deployments/<your-deployment-name>/chat/completions?api-version=<api-version>
ここで重要なのは、api-version
クエリパラメータの存在です。これを忘れると認証エラーや無効なリクエストとして弾かれることがあります。
2. 認証方式
Azure OpenAI Serviceでは、APIキーによる認証が主流です。APIキーはHTTPヘッダーにapi-key: YOUR_API_KEY
として含める必要があります。通常のOpenAI APIで使われるAuthorization: Bearer YOUR_API_KEY
とは異なる点に注意が必要です。
3. リクエストとレスポンスのJSON構造
a. リクエストボディ
Chat Completions API(gpt-3.5-turbo
, gpt-4
など)では、リクエストボディは以下のようなJSON構造をとります。messages
配列に会話履歴をオブジェクトとして渡すのが特徴です。
{
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Tell me a joke."}
],
"max_tokens": 100,
"temperature": 0.7
}
role
: system
, user
, assistant
のいずれか。system
はAIの振る舞いを定義、user
はユーザーの入力、assistant
はAIの応答を表します。
content
: メッセージ本体。
max_tokens
: 生成されるテキストの最大トークン数。
temperature
: 生成されるテキストのランダム性(創造性)を制御します。0.0から2.0の範囲で、高いほど多様な応答が生成されやすくなります。
b. レスポンスボディ
成功すると、以下のようなJSONが返されます。生成されたテキストはchoices[0].message.content
に格納されます。
{
"id": "chatcmpl-...",
"object": "chat.completion",
"created": 1677652288,
"model": "gpt-35-turbo",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Why don't scientists trust atoms? Because they make up everything!"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 17,
"completion_tokens": 16,
"total_tokens": 33
}
}
4. VBAにおけるCOMオブジェクトと64bit/32bitの差異
VBAからHTTP通信を行うには、通常、Microsoft WinHttpRequest Services
(WinHttpRequest
)やMicrosoft XML, v6.0
(MSXML2.XMLHTTP60
)といったCOMオブジェクトを利用します。これらはVBAの実行環境(32bit/64bit)によらず安定して動作します。
一方、Declare
ステートメントを使ってWindows API(WinAPI)を直接呼び出す場合、32bitと64bit環境でポインタのサイズが異なるため注意が必要です。64bit環境ではポインタが8バイトになるため、VBAではLongPtr
型を使用し、Declare
ステートメントにはPtrSafe
キーワードを付加する必要があります。
本記事で扱うHTTP通信においてはCOMオブジェクトを使うため直接PtrSafe
/LongPtr
の記述は必要ありませんが、VBAでより低レベルな操作を行う際には不可欠な知識です。
Azure OpenAI Chat Completions API 主要仕様
- エンドポイントURL構造:
https://<your-resource-name>.openai.azure.com/openai/deployments/<your-deployment-name>/chat/completions?api-version=YYYY-MM-DD
- HTTPメソッド:
- HTTPヘッダー:
Content-Type
: application/json
(必須)
api-key
: <your-api-key>
(必須、Azure OpenAI専用認証)
- リクエストボディ (JSON) 主要フィールド:
messages
: array
of object
(必須)
role
: string
(system
, user
, assistant
)
content
: string
max_tokens
: integer
(オプション, 生成トークン上限)
temperature
: number
(オプション, 0.0-2.0, 創造性)
top_p
: number
(オプション, 0.0-1.0, 候補トークンの選択範囲)
stop
: string
or array
of string
(オプション, 停止シーケンス)
- レスポンスボディ (JSON) 主要フィールド:
id
: string
(リクエストID)
object
: string
(chat.completion
)
choices
: array
of object
message
: object
role
: string
(assistant
)
content
: string
(生成されたテキスト)
finish_reason
: string
(stop
, length
など)
usage
: object
prompt_tokens
: integer
(プロンプトトークン数)
completion_tokens
: integer
(生成トークン数)
total_tokens
: integer
(合計トークン数)
実装(最小→堅牢化)
共通設定
以下の変数は、VBA/PowerShellともに自身の環境に合わせて設定してください。
' Azure OpenAI Serviceの設定
Const AZURE_OPENAI_RESOURCE_NAME As String = "YOUR_AZURE_OPENAI_RESOURCE_NAME" ' 例: my-openai-resource
Const AZURE_OPENAI_DEPLOYMENT_NAME As String = "YOUR_AZURE_OPENAI_DEPLOYMENT_NAME" ' 例: gpt-35-turbo
Const AZURE_OPENAI_API_KEY As String = "YOUR_AZURE_OPENAI_API_KEY"
Const AZURE_OPENAI_API_VERSION As String = "2023-05-15" ' 現在の推奨バージョン
# Azure OpenAI Serviceの設定
$AzureOpenAIResourceName = "YOUR_AZURE_OPENAI_RESOURCE_NAME" # 例: my-openai-resource
$AzureOpenAIDeploymentName = "YOUR_AZURE_OPENAI_DEPLOYMENT_NAME" # 例: gpt-35-turbo
$AzureOpenAIApiKey = "YOUR_AZURE_OPENAI_API_KEY"
$AzureOpenAIApiVersion = "2023-05-15" # 現在の推奨バージョン
VBAでの実装
最小実装
WinHttpRequest
オブジェクトを使用して、シンプルなテキスト生成を行います。JSONの構築とパースは、最も基本的な文字列操作で行います。
Attribute VB_Name = "Module1"
Option Explicit
Private Const AZURE_OPENAI_RESOURCE_NAME As String = "YOUR_AZURE_OPENAI_RESOURCE_NAME"
Private Const AZURE_OPENAI_DEPLOYMENT_NAME As String = "YOUR_AZURE_OPENAI_DEPLOYMENT_NAME"
Private Const AZURE_OPENAI_API_KEY As String = "YOUR_AZURE_OPENAI_API_KEY"
Private Const AZURE_OPENAI_API_VERSION As String = "2023-05-15"
Public Function CallAzureOpenAI(ByVal prompt As String) As String
Dim httpRequest As Object
Dim url As String
Dim requestBody As String
Dim responseText As String
Dim startPos As Long, endPos As Long
' URLの構築
url = "https://" & AZURE_OPENAI_RESOURCE_NAME & ".openai.azure.com/openai/deployments/" & _
AZURE_OPENAI_DEPLOYMENT_NAME & "/chat/completions?api-version=" & AZURE_OPENAI_API_VERSION
' リクエストボディの構築 (最小限のJSON文字列連結)
requestBody = "{""messages"": [{""role"": ""user"", ""content"": """ & Replace(prompt, """", """""") & """}], ""max_tokens"": 150, ""temperature"": 0.7}"
Set httpRequest = CreateObject("WinHttp.WinHttpRequest.5.1")
With httpRequest
.Open "POST", url, False ' 同期通信
.SetRequestHeader "Content-Type", "application/json"
.SetRequestHeader "api-key", AZURE_OPENAI_API_KEY
.Send requestBody
responseText = .ResponseText
End With
' レスポンスの簡易パース (contentだけを抜き出す)
' 厳密なJSONパースではないため、JSON構造が変わると破綻します。
startPos = InStr(responseText, """content"":""")
If startPos > 0 Then
startPos = startPos + Len("""content"":""")
' "が来るまで読み飛ばす
startPos = InStr(startPos, responseText, """")
If startPos > 0 Then
startPos = startPos + 1 ' 開始の " の次から
endPos = InStr(startPos, responseText, """") ' 次の " まで
If endPos > 0 Then
CallAzureOpenAI = Replace(Mid(responseText, startPos, endPos - startPos), "\n", vbCrLf)
Else
CallAzureOpenAI = "Error: content end quote not found."
End If
Else
CallAzureOpenAI = "Error: content start quote not found."
End If
Else
CallAzureOpenAI = "Error: 'content' field not found. Full response: " & responseText
End If
Set httpRequest = Nothing
End Function
' 実行例
Public Sub TestAzureOpenAICall()
Dim result As String
result = CallAzureOpenAI("今日の天気は?")
Debug.Print "AIの応答: " & result
End Sub
堅牢化実装
エラーハンドリング、タイムアウト設定、リトライ処理、より安全なJSONパース(正規表現を利用)、そしてVBAにおける64bit環境への考慮について説明します。
Attribute VB_Name = "Module1"
Option Explicit
Private Const AZURE_OPENAI_RESOURCE_NAME As String = "YOUR_AZURE_OPENAI_RESOURCE_NAME"
Private Const AZURE_OPENAI_DEPLOYMENT_NAME As String = "YOUR_AZURE_OPENAI_DEPLOYMENT_NAME"
Private Const AZURE_OPENAI_API_KEY As String = "YOUR_AZURE_OPENAI_API_KEY"
Private Const AZURE_OPENAI_API_VERSION As String = "2023-05-15"
' 堅牢化のための定数
Private Const MAX_RETRIES As Long = 3
Private Const RETRY_DELAY_SECONDS As Long = 5 ' 秒
Private Const REQUEST_TIMEOUT_SECONDS As Long = 60 ' 秒
' PtrSafeとLongPtrについて(WinAPIを直接呼び出す場合の考慮点)
' WinHttpRequestはCOMオブジェクトのため、直接PtrSafe/LongPtrは不要ですが、
' 広くVBAで外部DLL/APIを扱う場合の知識として明記します。
' #If VBA7 Then
' Private Declare PtrSafe Function GetPrivateProfileString Lib "kernel32" _
' Alias "GetPrivateProfileStringA" (ByVal lpAppName As String, _
' ByVal lpKeyName As String, ByVal lpDefault As String, _
' ByVal lpReturnedString As String, ByVal nSize As Long, _
' ByVal lpFileName As String) As Long
' #Else
' Private Declare Function GetPrivateProfileString Lib "kernel32" _
' Alias "GetPrivateProfileStringA" (ByVal lpAppName As String, _
' ByVal lpKeyName As String, ByVal lpDefault As String, _
' ByVal lpReturnedString As String, ByVal nSize As Long, _
' ByVal lpFileName As String) As Long
' #End If
' 上記のように、VBA7 (Office 2010以降の64bit版VBA) ではPtrSafeとLongPtrが必須となります。
' LongPtrはポインタを格納するための型で、32bit環境ではLong (4バイト)、64bit環境ではLongLong (8バイト) になります。
Public Function CallAzureOpenAI_Robust(ByVal prompt As String, Optional ByVal model As String = "gpt-35-turbo", Optional ByVal temperature As Single = 0.7) As String
Dim httpRequest As Object
Dim url As String
Dim requestBody As String
Dim responseText As String
Dim retryCount As Long
Dim regex As Object ' VBScript.RegExp
Dim matches As Object ' MatchCollection
Dim match As Object ' Match
Dim result As String
result = ""
' URLの構築
url = "https://" & AZURE_OPENAI_RESOURCE_NAME & ".openai.azure.com/openai/deployments/" & _
AZURE_OPENAI_DEPLOYMENT_NAME & "/chat/completions?api-version=" & AZURE_OPENAI_API_VERSION
' リクエストボディの構築関数
requestBody = BuildChatCompletionRequestBody(prompt, model, temperature)
Set httpRequest = CreateObject("WinHttp.WinHttpRequest.5.1")
With httpRequest
.SetRequestHeader "Content-Type", "application/json"
.SetRequestHeader "api-key", AZURE_OPENAI_API_KEY
.SetTimeouts REQUEST_TIMEOUT_SECONDS * 1000, REQUEST_TIMEOUT_SECONDS * 1000, REQUEST_TIMEOUT_SECONDS * 1000, REQUEST_TIMEOUT_SECONDS * 1000 ' Resolve, Connect, Send, Receive
For retryCount = 0 To MAX_RETRIES - 1
On Error GoTo ErrorHandler
.Open "POST", url, False ' 同期通信
.Send requestBody
' HTTPステータスコードを確認
If .Status >= 200 And .Status < 300 Then
responseText = .ResponseText
Exit For ' 成功したらループを抜ける
ElseIf .Status = 429 Or .Status >= 500 Then ' レートリミット (429) またはサーバーエラー (5xx)
Debug.Print "HTTP Error: " & .Status & " - " & .StatusText & ". Retrying in " & RETRY_DELAY_SECONDS & " seconds..."
Application.Wait Now + TimeValue("00:00:" & RETRY_DELAY_SECONDS)
If retryCount = MAX_RETRIES - 1 Then
Err.Raise 9999, "CallAzureOpenAI_Robust", "Max retries reached. Last HTTP Error: " & .Status & " - " & .StatusText
End If
Else ' その他のエラー
Err.Raise 9998, "CallAzureOpenAI_Robust", "HTTP Error: " & .Status & " - " & .StatusText & vbCrLf & "Response: " & .ResponseText
End If
ErrorHandler:
Debug.Print "Runtime Error: " & Err.Description & ". Retrying in " & RETRY_DELAY_SECONDS & " seconds..."
Application.Wait Now + TimeValue("00:00:" & RETRY_DELAY_SECONDS)
If retryCount = MAX_RETRIES - 1 Then
Err.Raise Err.Number, Err.Source, "Max retries reached. Last Error: " & Err.Description
End If
Resume Next ' エラー発生箇所から処理を再開
Next retryCount
End With
' レスポンスの堅牢なパース (VBScript.RegExpを利用)
Set regex = CreateObject("VBScript.RegExp")
With regex
.Pattern = """content""\s*:\s*""([^""]*)""" ' contentフィールドの値をキャプチャ
.Global = False ' 最初の一致のみ
.IgnoreCase = False
End With
If regex.Test(responseText) Then
Set matches = regex.Execute(responseText)
If matches.Count > 0 Then
' キャプチャグループ1がcontentの値
result = Replace(matches(0).SubMatches(0), "\n", vbCrLf) ' \nを改行に変換
CallAzureOpenAI_Robust = result
Else
CallAzureOpenAI_Robust = "Error: content not found in regex match. Raw response: " & responseText
End If
Else
CallAzureOpenAI_Robust = "Error: 'content' field not found in response using regex. Raw response: " & responseText
End If
Set httpRequest = Nothing
Set regex = Nothing
Set matches = Nothing
Set match = Nothing
Exit Function
' グローバルなエラーハンドラ(ループを抜けた後の最終的なエラー処理)
FinalErrorHandler:
CallAzureOpenAI_Robust = "Fatal Error: " & Err.Description & ". Last response: " & responseText
Set httpRequest = Nothing
Set regex = Nothing
Set matches = Nothing
Set match = Nothing
End Function
' リクエストボディを生成するヘルパー関数
Private Function BuildChatCompletionRequestBody(ByVal prompt As String, ByVal model As String, ByVal temperature As Single) As String
Dim jsonBuilder As String
jsonBuilder = "{"
jsonBuilder = jsonBuilder & """messages"": ["
jsonBuilder = jsonBuilder & "{""role"": ""user"", ""content"": """ & EscapeJsonString(prompt) & """}"
jsonBuilder = jsonBuilder & "],"
jsonBuilder = jsonBuilder & """max_tokens"": 150," ' 固定値としておく
jsonBuilder = jsonBuilder & """temperature"": " & Replace(CStr(temperature), ",", ".") & "" ' 小数点対応
jsonBuilder = jsonBuilder & "}"
BuildChatCompletionRequestBody = jsonBuilder
End Function
' JSON文字列のエスケープ処理
Private Function EscapeJsonString(ByVal inputString As String) As String
Dim escapedString As String
escapedString = Replace(inputString, "\", "\\")
escapedString = Replace(escapedString, """", "\""")
escapedString = Replace(escapedString, vbCrLf, "\n")
escapedString = Replace(escapedString, vbCr, "\r")
escapedString = Replace(escapedString, vbLf, "\n")
' 他にもエスケープが必要な文字があれば追加
EscapeJsonString = escapedString
End Function
' 実行例
Public Sub TestAzureOpenAICall_Robust()
Dim result As String
On Error GoTo TestError
result = CallAzureOpenAI_Robust("宇宙の果てはどうなっている?", "gpt-4", 0.9)
Debug.Print "AIの応答 (堅牢版): " & result
Exit Sub
TestError:
Debug.Print "テスト中にエラーが発生しました: " & Err.Description
End Sub
PowerShellでの実装
最小実装
Invoke-RestMethod
コマンドレットは、JSONのリクエスト/レスポンスの処理を大幅に簡略化してくれます。
# 最小実装
function Call-AzureOpenAI {
param (
[string]$Prompt
)
$AzureOpenAIResourceName = "YOUR_AZURE_OPENAI_RESOURCE_NAME"
$AzureOpenAIDeploymentName = "YOUR_AZURE_OPENAI_DEPLOYMENT_NAME"
$AzureOpenAIApiKey = "YOUR_AZURE_OPENAI_API_KEY"
$AzureOpenAIApiVersion = "2023-05-15"
$url = "https://$AzureOpenAIResourceName.openai.azure.com/openai/deployments/$AzureOpenAIDeploymentName/chat/completions?api-version=$AzureOpenAIApiVersion"
$headers = @{
"Content-Type" = "application/json"
"api-key" = $AzureOpenAIApiKey
}
$body = @{
messages = @(
@{ role = "user"; content = $Prompt }
)
max_tokens = 150
temperature = 0.7
} | ConvertTo-Json -Depth 4 # -Depthでネストされたオブジェクトも適切に変換
try {
$response = Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body $body -ContentType "application/json"
# PowerShellはレスポンスのJSONを自動的にオブジェクトに変換してくれる
$response.choices[0].message.content
}
catch {
Write-Error "Azure OpenAI API呼び出し中にエラーが発生しました: $($_.Exception.Message)"
$null
}
}
# 実行例
$result = Call-AzureOpenAI -Prompt "PowerShellでAzure OpenAIを使うメリットは何ですか?"
if ($result) {
Write-Host "AIの応答: $result"
}
堅牢化実装
エラーハンドリング、リトライ戦略、タイムアウト設定、そしてAPIキーのセキュアな管理に焦点を当てます。
# 堅牢化実装
function Call-AzureOpenAI-Robust {
param (
[string]$Prompt,
[string]$Model = "gpt-35-turbo",
[float]$Temperature = 0.7,
[int]$MaxRetries = 3,
[int]$RetryDelaySeconds = 5, # 秒
[int]$RequestTimeoutSeconds = 60 # 秒
)
$AzureOpenAIResourceName = "YOUR_AZURE_OPENAI_RESOURCE_NAME"
$AzureOpenAIDeploymentName = "YOUR_AZURE_OPENAI_DEPLOYMENT_NAME"
$AzureOpenAIApiVersion = "2023-05-15"
# APIキーの安全な管理: 環境変数から取得することを推奨
# $AzureOpenAIApiKey = $env:AZURE_OPENAI_API_KEY
# 環境変数に設定できない場合、Credentialオブジェクトを使用するなどの工夫が必要
# 例: $AzureOpenAIApiKey = (Get-Credential -UserName "azure-openai" -Message "Azure OpenAI API Key").Password
# ここではテストのため直接定義しますが、本番環境では必ず安全な方法を選んでください。
$AzureOpenAIApiKey = "YOUR_AZURE_OPENAI_API_KEY"
$url = "https://$AzureOpenAIResourceName.openai.azure.com/openai/deployments/$AzureOpenAIDeploymentName/chat/completions?api-version=$AzureOpenAIApiVersion"
$headers = @{
"Content-Type" = "application/json"
"api-key" = $AzureOpenAIApiKey
}
$body = @{
messages = @(
@{ role = "user"; content = $Prompt }
)
model = $Model
max_tokens = 150
temperature = $Temperature
} | ConvertTo-Json -Depth 4 # -Depthでネストされたオブジェクトも適切に変換
$attempt = 0
while ($attempt -lt $MaxRetries) {
try {
$response = Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body $body -ContentType "application/json" -TimeoutSec $RequestTimeoutSeconds -ErrorAction Stop
return $response.choices[0].message.content
}
catch {
$attempt++
Write-Warning "API呼び出し失敗 (試行 $attempt/$MaxRetries): $($_.Exception.Message)"
# HTTPステータスコードによるリトライ条件の判断
if ($_.Exception.Response) {
$statusCode = [int]$_.Exception.Response.StatusCode
$responseBody = (New-Object IO.StreamReader($_.Exception.Response.GetResponseStream())).ReadToEnd()
Write-Warning "HTTP Status Code: $statusCode, Response Body: $responseBody"
# 429 (Too Many Requests) または 5xx (Server Error) の場合にリトライ
if ($statusCode -eq 429 -or $statusCode -ge 500) {
if ($attempt -lt $MaxRetries) {
Write-Host "リトライのため $($RetryDelaySeconds) 秒待機します..."
Start-Sleep -Seconds $RetryDelaySeconds
}
}
else {
# その他のエラーはリトライせず終了
Write-Error "致命的なHTTPエラーが発生しました。リトライしません。"
return $null
}
}
else {
# ネットワークエラーなど、Responseオブジェクトがない場合もリトライ
if ($attempt -lt $MaxRetries) {
Write-Host "ネットワークエラーまたは不明なエラー。リトライのため $($RetryDelaySeconds) 秒待機します..."
Start-Sleep -Seconds $RetryDelaySeconds
}
}
}
}
Write-Error "最大リトライ回数に達しました。Azure OpenAI API呼び出しに失敗しました。"
return $null
}
# 実行例
$resultRobust = Call-AzureOpenAI-Robust -Prompt "猫が魚を捕まえる面白い話を作ってください。" -Model "gpt-4" -Temperature 0.9
if ($resultRobust) {
Write-Host "AIの応答 (堅牢版): $resultRobust"
}
ベンチ/検証
実務で利用する上で、API連携の性能と信頼性を評価することは不可欠です。
計測方法
- 応答速度: 各API呼び出しの開始からレスポンス受信までの時間を計測します。複数回実行し、平均値、最小値、最大値を記録します。
- 成功/失敗率: 指定期間内でのAPI呼び出し総数と、成功した呼び出し数(HTTP 2xx)、失敗した呼び出し数(HTTP 4xx, 5xx、ネットワークエラー、タイムアウト)を記録します。
- トークン使用量: レスポンスボディの
usage
フィールドから、prompt_tokens
、completion_tokens
、total_tokens
を取得し、コスト見積もりに利用します。
テスト観点
- プロンプトの長さと複雑性: 短いプロンプト、長いプロンプト、複雑な指示を含むプロンプトで応答速度や生成品質を比較します。
- 同時実行数(スループット): 複数のVBA/PowerShellプロセスやスレッドから同時にAPIを呼び出し、Azure OpenAI Serviceのレートリミットやスケーラビリティの影響を確認します。特にVBAではシングルスレッドなので、複数Excelインスタンス起動などの工夫が必要になります。
- エラーレスポンスの種類:
- APIキー不正/デプロイ名不一致 (401/404): 意図的に不正なキーやデプロイ名を指定し、適切なエラーハンドリングが行われるか確認。
- レートリミット (429): 短時間に大量のリクエストを送信し、リトライロジックが機能するか確認。
- 無効なプロンプト/パラメーター (400): 不適切なJSONや無効な
temperature
値などを送り、エラー応答を検証。
- ネットワーク遅延/タイムアウト: 意図的にネットワークを切断したり、極端に短いタイムアウトを設定し、処理が中断されずに適切にエラー処理されるか確認。
- 64bit/32bit環境での動作確認: VBAの場合、異なるOffice環境で
WinHttpRequest
などのCOMオブジェクトが正しくロード・動作するかを確認します。特にPtrSafe
やLongPtr
が関係するWinAPI呼び出しを含む場合は、より詳細なテストが必要です。
応用例/代替案
応用例
- Excelシートの自動要約/翻訳: Excelの特定列に入力されたテキストをAIが要約・翻訳し、別の列に結果を書き出す。報告書作成支援や多言語対応に活用。
- 定型業務レポート作成支援: 複数のデータソースから情報を集約し、AIが自然言語でレポートの草稿を生成。数値データに基づいた洞察のテキスト化。
- メールからの情報抽出と分類: 受信メールの内容をAIが解析し、重要な情報(日時、担当者、アクションアイテム)を抽出し、Excelや他のシステムに連携する。
- チャットボットのプロトタイピング: Excelシートを知識ベースとして、VBAマクロでユーザーからの問い合わせに応答する簡易チャットボット。
代替案
VBA/PowerShellでの直接連携には限界や制約も存在します。より大規模なシステムや高い信頼性が求められる場合は、以下の代替案も検討してください。
- Pythonによる連携:
- メリット:
requests
ライブラリによるHTTP通信の容易さ、openai
公式SDKの存在、豊富なJSON処理ライブラリ、Pythonコミュニティによる活発な開発。データサイエンス領域との親和性も高く、高度な前処理・後処理が容易。
- デメリット: 実行環境の構築が必要、Excel/Windowsバッチとの連携にはラッパーやプロセスの起動などの工夫が必要。
- Azure FunctionsやLogic Appsの活用:
- メリット: サーバーレスでスケーラブル、APIキーなどの機密情報を安全に管理できる(Key Vault連携)、HTTPトリガーやタイマートリガーなど柔軟な連携が可能。処理をクラウドにオフロードし、クライアント側の負荷を軽減。
- デメリット: クラウドサービスの構築・運用知識が必要、コスト管理が必要。
- Power Automate / Power Appsとの連携:
- メリット: ローコード/ノーコードでAzure OpenAI Serviceと連携可能。既存のMicrosoft 365サービス(SharePoint, Teams, Outlook)との統合が容易。
- デメリット: 細かい制御が難しい場合がある、ライセンス体系による制約。
まとめ
本記事では、VBAおよびPowerShellからAzure OpenAI ServiceのREST APIを連携させる具体的な方法を、最小実装から堅牢化、そしてその内部動作まで深掘りして解説しました。VBAにおけるWinHttpRequest
と文字列操作、PowerShellにおけるInvoke-RestMethod
とConvertTo-Json
は、それぞれ異なるアプローチでREST APIとの橋渡し役を果たします。
特に、64bit環境におけるPtrSafe
やLongPtr
の概念、APIキーの適切な管理、HTTPステータスコードに基づいたリトライ処理、そして正規表現を用いたJSONの堅牢なパースは、実務で安定したシステムを構築する上で不可欠な要素です。
これらの知見とコードを活用することで、あなたのExcelマクロやPowerShellスクリプトが、Azure OpenAI Serviceの強力なAI能力を手に入れ、より高度な業務自動化を実現できるでしょう。
運用チェックリスト
- [ ] APIキーは安全な方法(環境変数、Azure Key Vaultなど)で管理されているか?(コードに直接記述されていないか?)
- [ ] エンドポイントURLとAPIバージョンは最新かつ正確に設定されているか?
- [ ] HTTPヘッダー(特に
api-key
とContent-Type
)は正しく設定されているか?
- [ ] リクエストボディのJSONは仕様通りに構築され、エスケープ処理は適切に行われているか?
- [ ] HTTPステータスコードをチェックし、2xx系以外の場合に適切なエラーハンドリングが行われているか?
- [ ] レートリミット(HTTP 429)やサーバーエラー(HTTP 5xx)に対するリトライ処理(指数バックオフなど)は実装されているか?
- [ ] ネットワーク遅延やサービス側の応答遅延に備え、適切なタイムアウト設定が行われているか?
- [ ] レスポンスボディのJSONパースは堅牢に行われ、エラー応答や予期せぬJSON構造にも対応できるか?
- [ ] トークン使用量を監視し、コスト超過を防ぐ仕組みは検討されているか?
- [ ] プロンプトインジェクションなど、意図しないAIの挙動を引き起こすプロンプトに対する対策は検討されているか?
- [ ] ロギングは適切に実装されており、API呼び出しの成功/失敗、応答速度、エラー詳細が記録されているか?
- [ ] モデルのバージョンアップやAPI仕様変更があった際に、コードを柔軟に更新できる設計になっているか?
- [ ] VBAの場合、32bit/64bit環境で差異なく動作することが確認されているか?
参考リンク
Mermaid 図
sequenceDiagram
participant Client
participant Server
Client ->> Server: リクエスト
Server -->> Client: レスポンス
コメント