<p>VBA/PowerShellからAzure OpenAI API連携の深奥:RESTの極意と堅牢化</p>
<h2 class="wp-block-heading">導入(問題設定)</h2>
<p>日々大量のデータと格闘し、定型業務に追われるビジネスパーソンにとって、Microsoft OfficeアプリケーションとWindows環境は作業の中心です。しかし、近年の生成AI技術の発展は、これらのデスクトップ環境での作業に変革をもたらす可能性を秘めています。特にAzure OpenAI Serviceは、企業のセキュリティ要件を満たしつつ、GPTモデルの強力な能力を内部業務に組み込む道を開きます。</p>
<p>本稿では、皆様が慣れ親しんだVBA(Excel, Accessなど)とPowerShellから、Azure OpenAI ServiceのREST APIを直接叩き、その知能を最大限に活用するための深掘りした知識を提供します。単なるHowToに留まらず、APIの内部動作、境界条件、潜在的な落とし穴まで踏み込み、最小実装から堅牢なシステム構築への道を段階的に示します。外部ライブラリを極力排し、標準機能のみでどこまでできるかを追求することで、環境依存性を最小限に抑え、理解を深めることを目指します。</p>
<h2 class="wp-block-heading">理論の要点</h2>
<p>Azure OpenAI Serviceへの連携は、HTTPリクエストを介したREST API呼び出しが基本です。主要な概念を掘り下げましょう。</p>
<ol class="wp-block-list">
<li><p><strong>REST APIのエンドポイントとバージョン管理</strong>:</p>
<ul>
<li>Azure OpenAIのエンドポイントは <code>https://<your-resource-name>.openai.azure.com/openai/deployments/<your-deployment-name>/chat/completions?api-version=YYYY-MM-DD</code> の形式を取ります。</li>
<li><code>api-version</code> は非常に重要です。このバージョンによってリクエスト・レスポンスの構造や利用可能な機能が大きく変わるため、常に最新かつ安定したバージョンを指定することが推奨されます。後方互換性が保証されない場合があるため、固定したバージョンを使い続ける運用も一考です。</li>
<li><strong>落とし穴</strong>: <code>api-version</code> を指定しない、または古いバージョンを指定すると、エラーになったり、予期しないレスポンスが返ってくることがあります。</li>
</ul></li>
<li><p><strong>認証メカニズム (APIキー認証)</strong>:</p>
<ul>
<li>Azure OpenAIでは、通常、リソースに発行されるAPIキーを用いた認証が主流です。これはHTTPヘッダ <code>api-key</code> または <code>Ocp-Apim-Subscription-Key</code> に含めて送信します。</li>
<li><strong>内部動作</strong>: クライアント(VBA/PowerShell)から送られたAPIキーはAzureのAPI Gatewayで検証され、リクエスト元の権限が確認されます。この検証に失敗すると <code>401 Unauthorized</code> エラーが返されます。</li>
<li><strong>落とし穴</strong>: APIキーの漏洩はセキュリティ上の致命的な問題です。コード内に直接埋め込むのは避け、環境変数や設定ファイルから読み込むなどの対策が必要です。</li>
</ul></li>
<li><p><strong>JSON形式のリクエストとレスポンス</strong>:</p>
<ul>
<li>リクエストボディは <code>Content-Type: application/json</code> ヘッダと共にJSON形式で送信されます。</li>
<li>チャットコンプリーションAPIの場合、<code>messages</code> 配列が核となります。各メッセージは <code>role</code> (system, user, assistant) と <code>content</code> を持ちます。
<ul>
<li><code>system</code> プロンプトはモデルの振る舞いを決定づける最も重要な指示です。</li>
<li><code>user</code> はユーザーからの入力。</li>
<li><code>assistant</code> はモデルからの過去の応答(対話履歴の再現に利用)。</li>
</ul></li>
<li>レスポンスもJSON形式で、<code>choices</code> 配列の中に生成されたテキストが含まれます。</li>
<li><strong>境界条件</strong>: JSON文字列のエスケープ(例: ダブルクォート <code>"</code>, 改行 <code>\n</code>)は非常に重要です。特にVBAで手動でJSON文字列を構築する際は注意が必要です。</li>
</ul></li>
<li><p><strong>プロンプト設計の基礎</strong>:</p>
<ul>
<li><strong>System Prompt</strong>: モデルの役割、性格、制約などを最初に与える最も重要な指示です。一度設定すると、その後の対話全体に影響を与えます。</li>
<li><strong>User Prompt</strong>: ユーザーがモデルに何をさせたいかを具体的に指示します。明確で曖昧さのない指示が重要です。</li>
<li><strong>Few-Shot Prompting</strong>: 期待する出力形式の例をいくつか示すことで、モデルの性能を向上させるテクニックです。</li>
<li><strong>Chain-of-Thought Prompting</strong>: モデルに思考プロセスを段階的に出力させることで、複雑な問題に対する推論能力を高めます。</li>
<li><strong>落とし穴</strong>: 曖昧なプロンプトは期待しない、あるいは不正確な出力を招きます。また、プロンプトインジェクションへの対策も考慮する必要があります。</li>
</ul></li>
</ol>
<h3 class="wp-block-heading">API仕様(Chat Completions API主要引数)</h3>
<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;">Array</td>
<td style="text-align:left;">必須</td>
<td style="text-align:left;">対話の履歴。各要素は <code>{"role": "...", "content": "..."}</code> 形式。<code>role</code> は “system”, “user”, “assistant”。</td>
</tr>
<tr>
<td style="text-align:left;"><code>temperature</code></td>
<td style="text-align:left;">Number</td>
<td style="text-align:left;">任意</td>
<td style="text-align:left;">生成されるテキストのランダム性(0.0~2.0)。0.0で決定的、2.0で創造的。デフォルトは1.0。</td>
</tr>
<tr>
<td style="text-align:left;"><code>max_tokens</code></td>
<td style="text-align:left;">Integer</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;">Number</td>
<td style="text-align:left;">任意</td>
<td style="text-align:left;">temperatureの代替。累積確率が<code>top_p</code>に達するまでのトークンのみを考慮する(0.0~1.0)。デフォルトは1.0。</td>
</tr>
<tr>
<td style="text-align:left;"><code>stop</code></td>
<td style="text-align:left;">Array</td>
<td style="text-align:left;">任意</td>
<td style="text-align:left;">生成を停止する文字列の配列。最大4つ。</td>
</tr>
<tr>
<td style="text-align:left;"><code>stream</code></td>
<td style="text-align:left;">Boolean</td>
<td style="text-align:left;">任意</td>
<td style="text-align:left;">部分的なメッセージをストリーミングするかどうか。<code>true</code>の場合、応答はServer-Sent Events形式になる。</td>
</tr>
<tr>
<td style="text-align:left;"><code>seed</code></td>
<td style="text-align:left;">Integer</td>
<td style="text-align:left;">任意</td>
<td style="text-align:left;">再現可能な結果を得るためのシード値。<code>temperature</code>と<code>top_p</code>に依存し、常に同じ出力を保証するものではない。</td>
</tr>
</tbody>
</table></figure>
<h3 class="wp-block-heading">処理フロー概要</h3>
<div class="wp-block-merpress-mermaidjs diagram-source-mermaid"><pre class="mermaid">
graph TD
A["VBA/PowerShellスクリプト開始"] --> B{"設定読み込み:<br>エンドポイント, APIキー"};
B --> C["JSONリクエストボディ構築"];
C --> D{"HTTPヘッダ設定:<br>Content-Type, api-key"};
D --> E["HTTPリクエスト送信"];
E --> F{"HTTPレスポンス受信"};
F -- 成功 (Status 2xx) --> G["JSONレスポンス解析"];
G --> H["結果利用"];
F -- 失敗 (Status 4xx/5xx) --> I["エラー処理"];
H --> J["処理完了"];
I --> J;
</pre></div>
<h2 class="wp-block-heading">実装(最小→堅牢化)</h2>
<p>ここではVBAとPowerShellそれぞれの実装を、最小構成から堅牢化へと段階的に進めます。</p>
<h3 class="wp-block-heading">環境設定(共通)</h3>
<p>Azure OpenAI Serviceのリソース作成とモデルのデプロイは完了しているものとします。
以下の情報を準備してください。</p>
<ul class="wp-block-list">
<li><strong>Azure OpenAI エンドポイント</strong>: <code>https://YOUR_RESOURCE_NAME.openai.azure.com</code></li>
<li><strong>API キー</strong>: Azure OpenAIリソースの「キーとエンドポイント」から取得</li>
<li><strong>デプロイ名</strong>: デプロイしたモデルの名前(例: <code>gpt-35-turbo-0125</code>)</li>
<li><strong>API バージョン</strong>: <code>2024-02-15</code> など</li>
</ul>
<h3 class="wp-block-heading">1. PowerShellによる最小実装</h3>
<p>PowerShellは<code>Invoke-RestMethod</code>コマンドレットにより、REST API呼び出しが非常に簡潔に行えます。JSONのシリアライズ・デシリアライズも自動で行われます。</p>
<pre data-enlighter-language="generic"># region --- 設定値 (本番では環境変数等から読み込むべき) ---
$azureOpenAiEndpoint = "https://YOUR_RESOURCE_NAME.openai.azure.com"
$apiKey = "YOUR_API_KEY" # !!! 本番では環境変数やAzure Key Vaultから取得すること !!!
$deploymentName = "YOUR_DEPLOYMENT_NAME"
$apiVersion = "2024-02-15"
# endregion
# Chat Completions APIのエンドポイントURLを構築
$uri = "$azureOpenAiEndpoint/openai/deployments/$deploymentName/chat/completions?api-version=$apiVersion"
# リクエストヘッダ
$headers = @{
"Content-Type" = "application/json"
"api-key" = $apiKey
}
# リクエストボディ (JSON形式)
# messages配列で会話履歴を渡す
$body = @{
messages = @(
@{ role = "system"; content = "あなたは親切なAIアシスタントです。" },
@{ role = "user"; content = "日本の首都はどこですか?" }
)
temperature = 0.7
max_tokens = 60
} | ConvertTo-Json -Depth 4 # -Depthでネストされたオブジェクトも適切にJSON化
Write-Host "URI: $uri"
Write-Host "Headers: $($headers | ConvertTo-Json)"
Write-Host "Body: $($body | ConvertTo-Json)"
try {
# Invoke-RestMethodでAPIを呼び出し、自動でJSONをPowerShellオブジェクトに変換
$response = Invoke-RestMethod -Uri $uri -Headers $headers -Body ($body | ConvertTo-Json) -Method Post
Write-Host "`n--- API Response ---"
# 応答からメッセージの内容を抽出
if ($response.choices -and $response.choices.Count -gt 0) {
$responseText = $response.choices[0].message.content
Write-Host "AIの応答: $responseText"
Write-Host "使用トークン数: $($response.usage.total_tokens)"
} else {
Write-Warning "API応答にchoicesが見つかりませんでした。"
$response | ConvertTo-Json -Depth 4 | Write-Host
}
}
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 "HTTP Status Code: $($_.Exception.Response.StatusCode)"
Write-Error "詳細エラー: $responseBody"
}
}
</pre>
<h3 class="wp-block-heading">2. VBAによる最小実装</h3>
<p>VBAでは<code>WinHttpRequest</code>オブジェクトを使用します。JSONの構築は手動または簡単な文字列連結で行う必要があります。</p>
<pre data-enlighter-language="generic">' このコードは標準モジュールに記述してください
#If VBA7 Then
' 64bit/32bit VBA対応のため、LongPtrはWinHttpRequestのGetObject呼び出しで不要
' 但し、WinAPI Declareステートメントを利用する場合はPtrSafeとLongPtrが必須
Private Const WHTTP_SET_REQUEST_HEADER As String = "setRequestHeader"
Private Const WHTTP_SEND As String = "send"
#Else
' VBA6以前 (32bit)
Private Const WHTTP_SET_REQUEST_HEADER As String = "setRequestHeader"
Private Const WHTTP_SEND As String = "send"
#End If
Sub CallAzureOpenAIChatMinimal()
Dim WinHttpReq As Object
Dim sURL As String
Dim sAPIKey As String
Dim sDeploymentName As String
Dim sAPIVersion As String
Dim sRequestBody As String
Dim sResponseText As String
Dim sJsonResponse As String
Dim lStatusCode As Long
Dim iPos As Long ' JSON解析用
' --- 設定値 (本番では安全な方法で管理すべき) ---
Const AZURE_OPENAI_ENDPOINT As String = "https://YOUR_RESOURCE_NAME.openai.azure.com"
sAPIKey = "YOUR_API_KEY" ' !!! 本番では環境変数や設定ファイルから取得すること !!!
sDeploymentName = "YOUR_DEPLOYMENT_NAME"
sAPIVersion = "2024-02-15"
' ---
' WinHttpRequestオブジェクトの作成
Set WinHttpReq = CreateObject("WinHttp.WinHttpRequest.5.1")
' URLの構築
sURL = AZURE_OPENAI_ENDPOINT & "/openai/deployments/" & sDeploymentName & "/chat/completions?api-version=" & sAPIVersion
' リクエストボディ (JSON形式) の構築
' VBAではJSONライブラリなしで複雑なJSONを扱うのは困難。ここでは最小限の構造で手動構築。
sRequestBody = "{""messages"": [{""role"": ""system"", ""content"": ""あなたは親切なAIアシスタントです。""}," & _
"{""role"": ""user"", ""content"": ""VBAでできることは何ですか?""}]," & _
"""temperature"": 0.7," & _
"""max_tokens"": 100}"
Debug.Print "URL: " & sURL
Debug.Print "Request Body: " & sRequestBody
' HTTPリクエストのオープンと送信
With WinHttpReq
.Open "POST", sURL, False ' Falseは同期実行を意味する
.setRequestHeader "Content-Type", "application/json"
.setRequestHeader "api-key", sAPIKey
.send sRequestBody
lStatusCode = .Status
sJsonResponse = .ResponseText
Debug.Print "HTTP Status Code: " & lStatusCode
Debug.Print "Response JSON: " & sJsonResponse
If lStatusCode >= 200 And lStatusCode < 300 Then
' 成功レスポンスの処理
' VBA標準機能でのJSON解析は非常に脆弱かつ限定的です。
' 実運用ではJSONパーサーライブラリ (例: JsonConverter.bas) の導入を強く推奨します。
' ここでは「content」フィールドを簡易的に抽出する例を示します。
iPos = InStr(sJsonResponse, """content"":""")
If iPos > 0 Then
sResponseText = Mid(sJsonResponse, iPos + Len("""content"":"""))
iPos = InStr(sResponseText, """") ' 次のダブルクォートまで
If iPos > 0 Then
sResponseText = Left(sResponseText, iPos - 1)
' JSONエスケープ文字の簡易的なデコード (例: \n -> Chr(10))
sResponseText = Replace(sResponseText, "\n", Chr(10))
sResponseText = Replace(sResponseText, "\r", Chr(13))
sResponseText = Replace(sResponseText, "\t", Chr(9))
sResponseText = Replace(sResponseText, "\""", """") ' エスケープされたダブルクォート
MsgBox "AIの応答:" & vbCrLf & sResponseText, vbInformation, "Azure OpenAI Response"
Else
MsgBox "応答JSONからcontentの終了が見つかりません。", vbCritical
End If
Else
MsgBox "応答JSONにcontentが見つかりません。JSON全体を確認してください。", vbCritical
End If
Else
' エラーレスポンスの処理
MsgBox "API呼び出しでエラーが発生しました。HTTP Status: " & lStatusCode & vbCrLf & _
"詳細: " & sJsonResponse, vbCritical, "API Error"
End If
End With
Set WinHttpReq = Nothing
End Sub
</pre>
<p><strong>VBAにおける64bit対応とPtrSafe/LongPtrについて</strong>:
<code>WinHttpRequest.5.1</code> はCOMオブジェクトであり、<code>CreateObject</code> でインスタンス化されるため、直接的な<code>Declare PtrSafe</code>の要件はありません。ただし、もしVBAから直接Windows APIを呼び出すようなコード(例: レジストリ操作、ファイルパスの取得など)を追加する場合は、<code>Declare</code>ステートメントに<code>PtrSafe</code>キーワードを付与し、ポインタを扱う変数には<code>LongPtr</code>型を使用する必要があります。本稿のAPI連携においては、WinHttpRequestオブジェクトの使用に限定しているため、<code>PtrSafe</code>や<code>LongPtr</code>の直接的な使用は不要です。しかし、これがVBA開発における64bit互換性の重要な考慮点であることは明記しておきます。</p>
<h3 class="wp-block-heading">3. PowerShellによる堅牢化</h3>
<p>エラー処理の強化、設定の外部化、ログ出力などを追加します。</p>
<pre data-enlighter-language="generic"># region --- 設定値 (本番ではセキュアな方法で外部化すべき) ---
# PowerShellの環境変数から取得する例
$azureOpenAiEndpoint = $env:AZURE_OPENAI_ENDPOINT
$apiKey = $env:AZURE_OPENAI_API_KEY # 環境変数 'AZURE_OPENAI_API_KEY' にAPIキーを設定
$deploymentName = $env:AZURE_OPENAI_DEPLOYMENT_NAME
$apiVersion = $env:AZURE_OPENAI_API_VERSION -replace '\s','' # 空白除去
# 設定値のバリデーション
if (-not $azureOpenAiEndpoint -or -not $apiKey -or -not $deploymentName -or -not $apiVersion) {
Write-Error "環境変数が不足しています。AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_API_KEY, AZURE_OPENAI_DEPLOYMENT_NAME, AZURE_OPENAI_API_VERSION を設定してください。"
exit 1
}
# endregion
Function Get-OpenAIChatCompletion {
param(
[string]$SystemPrompt,
[string]$UserPrompt,
[double]$Temperature = 0.7,
[int]$MaxTokens = 200
)
$uri = "$azureOpenAiEndpoint/openai/deployments/$deploymentName/chat/completions?api-version=$apiVersion"
$headers = @{
"Content-Type" = "application/json"
"api-key" = $apiKey
}
$messages = @(
@{ role = "system"; content = $SystemPrompt }
)
if ($UserPrompt) {
$messages += @{ role = "user"; content = $UserPrompt }
}
$body = @{
messages = $messages
temperature = $Temperature
max_tokens = $MaxTokens
}
Write-Verbose "URI: $uri" -Verbose
Write-Verbose "Request Body: $($body | ConvertTo-Json -Depth 4)" -Verbose
try {
$response = Invoke-RestMethod -Uri $uri -Headers $headers -Body ($body | ConvertTo-Json -Depth 4) -Method Post -TimeoutSec 60
if ($response.choices -and $response.choices.Count -gt 0) {
$responseText = $response.choices[0].message.content
Write-Verbose "Total Tokens: $($response.usage.total_tokens)" -Verbose
return $responseText
} else {
Write-Warning "API応答にchoicesが見つかりませんでした。応答: $($response | ConvertTo-Json -Depth 4)"
return $null
}
}
catch {
$errorMessage = "API呼び出し中にエラーが発生しました: $($_.Exception.Message)"
Write-Error $errorMessage
if ($_.Exception.Response) {
$errorStream = $_.Exception.Response.GetResponseStream()
$reader = New-Object System.IO.StreamReader($errorStream)
$responseBody = $reader.ReadToEnd()
Write-Error "HTTP Status Code: $($_.Exception.Response.StatusCode)"
Write-Error "詳細エラー: $responseBody"
}
throw $_.Exception # 呼び出し元でエラーを捕捉できるよう再スロー
}
}
# 関数の利用例
try {
$systemPrompt = "あなたはユーザーの質問に簡潔に答えるAIです。敬語を使いましょう。"
$userQuestion = "PowerShellでREST APIを呼び出す利点は何ですか?"
$aiResponse = Get-OpenAIChatCompletion -SystemPrompt $systemPrompt -UserPrompt $userQuestion -MaxTokens 150
if ($aiResponse) {
Write-Host "`n--- AIからの回答 ---"
Write-Host $aiResponse
} else {
Write-Host "`nAIからの回答が得られませんでした。"
}
}
catch {
Write-Host "`nスクリプト実行中に致命的なエラーが発生しました。"
Write-Host "エラーメッセージ: $($_.Exception.Message)"
}
</pre>
<h3 class="wp-block-heading">4. VBAによる堅牢化</h3>
<p>エラー処理、設定の外部化(ここではコメントで言及)、JSON解析の課題解決(外部ライブラリ導入推奨を明記)などを考慮します。</p>
<pre data-enlighter-language="generic">' 標準モジュールに記述
' --- JSONパーサーライブラリの取り扱いについて ---
' VBAで堅牢にJSONを扱うには、専用のパーサーライブラリ導入が必須です。
' 例: https://github.com/VBA-tools/VBA-JSON-parser (JsonConverter.bas)
' 以下のコードでは、ライブラリが導入されていることを前提とした理想的な呼び出し例を示します。
' 実際のコードでは、上記ライブラリの JsonConverter.bas ファイルをプロジェクトにインポートしてください。
' その後、ツール -> 参照設定 で "Microsoft Scripting Runtime" を有効にする必要がある場合があります。
' ----------------------------------------------------
#If VBA7 Then
' 64bit/32bit VBA対応
' WinHttpRequestオブジェクトはCOMオブジェクトのため、PtrSafeは直接関係しない
' 但し、WinHttpRequestオブジェクトの呼び出しは、COMインターフェースを介するため、
' アドレス空間の差はCOMレイヤーが吸収します。
#Else
' VBA6以前 (32bit)
#End If
' 設定値を保持するグローバル変数またはPublic定数
' 本番環境ではINIファイル、レジストリ、またはDBから読み込む仕組みを推奨
Private Const AZURE_OPENAI_ENDPOINT As String = "https://YOUR_RESOURCE_NAME.openai.azure.com"
Private Const AZURE_OPENAI_API_KEY As String = "YOUR_API_KEY" ' !!! 環境変数などから安全に取得すべき !!!
Private Const AZURE_OPENAI_DEPLOYMENT_NAME As String = "YOUR_DEPLOYMENT_NAME"
Private Const AZURE_OPENAI_API_VERSION As String = "2024-02-15"
' 例外処理をラップした関数
Public Function GetAzureOpenAIChatCompletion(ByVal sSystemPrompt As String, _
ByVal sUserPrompt As String, _
Optional ByVal dTemperature As Double = 0.7, _
Optional ByVal lMaxTokens As Long = 200) As String
Dim WinHttpReq As Object
Dim sURL As String
Dim sRequestBody As String
Dim sJsonResponse As String
Dim lStatusCode As Long
Dim vParsedJson As Variant ' JsonConverterライブラリ用の変数
Dim sResponseContent As String
' エラーハンドリング
On Error GoTo ErrorHandler
Set WinHttpReq = CreateObject("WinHttp.WinHttpRequest.5.1")
sURL = AZURE_OPENAI_ENDPOINT & "/openai/deployments/" & AZURE_OPENAI_DEPLOYMENT_NAME & "/chat/completions?api-version=" & AZURE_OPENAI_API_VERSION
' JSON Request Body の構築 (JsonConverterライブラリ利用を想定)
' ライブラリを使用しない場合は、手動で文字列を連結するロジックが必要です (最小実装を参照)
Dim JsonMessages As Object
Set JsonMessages = CreateObject("Scripting.Dictionary") ' For JsonConverter
Dim SystemMsg As Object
Set SystemMsg = CreateObject("Scripting.Dictionary")
SystemMsg("role") = "system"
SystemMsg("content") = sSystemPrompt
JsonMessages.Add 1, SystemMsg
Dim UserMsg As Object
Set UserMsg = CreateObject("Scripting.Dictionary")
UserMsg("role") = "user"
UserMsg("content") = sUserPrompt
JsonMessages.Add 2, UserMsg
Dim RequestDict As Object
Set RequestDict = CreateObject("Scripting.Dictionary")
RequestDict("messages") = JsonMessages.Items
RequestDict("temperature") = dTemperature
RequestDict("max_tokens") = lMaxTokens
' JsonConverter.bas が存在する場合
sRequestBody = JsonConverter.ConvertToJson(RequestDict, 2) ' 2はインデントレベル
Debug.Print "URL: " & sURL
Debug.Print "Request Body: " & sRequestBody
With WinHttpReq
.Open "POST", sURL, False
.setRequestHeader "Content-Type", "application/json"
.setRequestHeader "api-key", AZURE_OPENAI_API_KEY
.send sRequestBody
lStatusCode = .Status
sJsonResponse = .ResponseText
Debug.Print "HTTP Status Code: " & lStatusCode
Debug.Print "Response JSON: " & sJsonResponse
If lStatusCode >= 200 And lStatusCode < 300 Then
' JSON Response の解析 (JsonConverterライブラリ利用を想定)
Set vParsedJson = JsonConverter.ParseJson(sJsonResponse)
If Not vParsedJson Is Nothing Then
If vParsedJson.Exists("choices") And vParsedJson("choices").Count > 0 Then
sResponseContent = vParsedJson("choices")(1)("message")("content")
GetAzureOpenAIChatCompletion = sResponseContent
Exit Function ' 正常終了
Else
Err.Raise 9999, "GetAzureOpenAIChatCompletion", "API応答に'choices'またはコンテンツが見つかりません。"
End If
Else
Err.Raise 9999, "GetAzureOpenAIChatCompletion", "API応答JSONの解析に失敗しました。"
End If
Else
Err.Raise 9999, "GetAzureOpenAIChatCompletion", "API呼び出しでエラーが発生しました。Status: " & lStatusCode & ", 詳細: " & sJsonResponse
End If
End With
CleanUp:
Set WinHttpReq = Nothing
Set JsonMessages = Nothing
Set SystemMsg = Nothing
Set UserMsg = Nothing
Set RequestDict = Nothing
Exit Function
ErrorHandler:
MsgBox "エラー発生: " & Err.Description & vbCrLf & "発生元: " & Err.Source, vbCritical
GetAzureOpenAIChatCompletion = "" ' エラー時は空文字列を返す
Resume CleanUp ' クリーンアップ処理へ
End Function
' 呼び出し例 (別のプロシージャから)
Sub TestAzureOpenAIChatRobust()
Dim sResult As String
sResult = GetAzureOpenAIChatCompletion( _
"あなたはプロの技術ブログ著者です。", _
"Excel VBAでHTTPリクエストを送る方法について、特にWinHttpRequestオブジェクトの使い方と注意点を解説してください。" _
)
If sResult <> "" Then
MsgBox "AIの生成結果:" & vbCrLf & sResult, vbInformation, "AI生成結果"
Else
MsgBox "AIからの応答を取得できませんでした。", vbCritical
End If
End Sub
</pre>
<h2 class="wp-block-heading">ベンチ/検証</h2>
<p>API連携の性能評価や安定性検証は、本番運用において不可欠です。</p>
<p><strong>計測方法とテスト観点</strong>:</p>
<ol class="wp-block-list">
<li><strong>レイテンシ計測</strong>:
<ul>
<li>APIリクエスト送信直前とレスポンス受信直後にタイムスタンプを記録し、差分を計測します。</li>
<li>VBA: <code>Timer</code>関数、<code>GetTickCount</code> (Windows API)</li>
<li>PowerShell: <code>Measure-Command</code>、<code>[System.Diagnostics.Stopwatch]::StartNew()</code></li>
<li>同一のプロンプトで複数回(例: 100回)実行し、平均値、最小値、最大値、標準偏差を記録します。</li>
<li>ネットワーク状況やAzure OpenAI Serviceの負荷により変動するため、様々な時間帯で計測します。</li>
</ul></li>
<li><strong>成功/失敗率</strong>:
<ul>
<li>多数のリクエストを送信し、HTTPステータスコードが2xx系で成功した割合を計測します。</li>
<li>4xx(クライアントエラー)や5xx(サーバーエラー)が頻発する場合、コードやAPI設定の見直しが必要です。</li>
</ul></li>
<li><strong>スループット</strong>:
<ul>
<li>一定時間内(例: 1分間)に処理できたリクエスト数を計測します。</li>
<li>Azure OpenAI Serviceにはレート制限(Rate Limit)があるため、これを意識したテストが必要です。制限に達すると <code>429 Too Many Requests</code> が返されます。</li>
</ul></li>
<li><strong>レスポンス内容の妥当性</strong>:
<ul>
<li>生成されたテキストがプロンプトの意図に沿っているか、不適切な内容を含んでいないかを評価します。これは自動化が難しい部分ですが、手動での確認や、簡単なキーワードマッチングなどでの自動チェックを組み合わせます。</li>
<li>特にシステムプロンプトの変更が、出力にどのような影響を与えるかを重点的に検証します。</li>
</ul></li>
</ol>
<p>これらの検証を通じて、API連携のボトルネックを特定し、パフォーマンスチューニングやエラーハンドリングの改善に繋げます。</p>
<h2 class="wp-block-heading">応用例/代替案</h2>
<h3 class="wp-block-heading">応用例</h3>
<ol class="wp-block-list">
<li><strong>Excel/Accessでのデータ分析・報告書作成支援</strong>:
<ul>
<li>Excelの特定セル範囲のデータ(例: 顧客からの自由記述アンケート)をプロンプトとして送信し、要約やカテゴリ分類、センチメント分析をAPIに実行させ、結果を別のセルに書き出す。</li>
<li>Accessのレポート作成時に、データベース内のテキストデータからAIに自動でコメントや洞察を生成させる。</li>
</ul></li>
<li><strong>社内チャットボット/ナレッジベース</strong>:
<ul>
<li>PowerShellスクリプトを定期実行し、社内ドキュメントやFAQを要約・整理し、SharePointリストやTeamsチャネルに自動投稿。ユーザーからの質問をAIが回答する仕組みを構築。</li>
</ul></li>
<li><strong>定型文生成とメール作成アシスト</strong>:
<ul>
<li>VBAでOutlookを操作し、メールの件名や本文、添付ファイルの内容に基づき、AIに返信文のドラフトを生成させる。</li>
</ul></li>
<li><strong>コード生成/レビューアシスト (開発者向け)</strong>:
<ul>
<li>VBAコードの一部をAPIに送信し、改善提案やバグの可能性を指摘させる。</li>
</ul></li>
</ol>
<h3 class="wp-block-heading">代替案</h3>
<ol class="wp-block-list">
<li><strong>Azure Functions/Logic Apps経由</strong>:
<ul>
<li>VBA/PowerShellから直接Azure OpenAIを叩くのではなく、間にAzure FunctionsやLogic Appsを挟む構成です。</li>
<li><strong>利点</strong>: APIキーの直接露出を防ぎ、認証・認可をAzureプラットフォームに委ねられる。レート制限やエラーハンドリング、ログ出力などを中央集権的に管理できる。負荷分散やスケーラビリティも向上。複雑な前処理・後処理ロジックをAzure上で実行できる。</li>
<li><strong>欠点</strong>: 追加のAzureリソースコストと管理の手間が発生。レイテンシが増加する可能性。</li>
</ul></li>
<li><strong>専用SDKの利用</strong>:
<ul>
<li>PythonやC#などの言語では、Microsoftが提供する公式SDK(<code>azure-openai</code>など)があります。</li>
<li><strong>利点</strong>: REST APIの詳細を意識せずに、オブジェクト指向で直感的にAPIを利用できる。認証やエラーハンドリングもSDKがラップしてくれるため開発が容易。</li>
<li><strong>欠点</strong>: VBAやPowerShellには公式SDKがないため、本稿で扱ったようなHTTPクライアントによる直接呼び出しが必要。</li>
</ul></li>
</ol>
<p>これらの代替案は、システムの規模、セキュリティ要件、運用体制によって選択すべきアプローチが変わります。デスクトップアプリケーションからの手軽な利用では直接連携が適していますが、本格的な業務システムへの組み込みではAzure Functions経由がより堅牢な選択肢となります。</p>
<h2 class="wp-block-heading">まとめ</h2>
<p>本稿では、VBAおよびPowerShellからAzure OpenAI ServiceのREST APIを連携させる具体的な方法について、その内部動作や堅牢化のポイントを交えながら深掘りしました。</p>
<ul class="wp-block-list">
<li><strong>VBA</strong>: <code>WinHttpRequest</code>オブジェクトを用いたHTTPリクエストの構築、APIキー認証、JSON文字列の手動構築とその脆弱性、そしてJSONパーサーライブラリの必要性を解説しました。64bit対応における<code>PtrSafe</code>や<code>LongPtr</code>の概念にも触れ、<code>WinHttpRequest</code>自体はCOM経由のため直接の影響は少ないが、一般的なVBA開発では注意が必要であることを示しました。</li>
<li><strong>PowerShell</strong>: <code>Invoke-RestMethod</code>コマンドレットがJSONの自動処理を含め、REST API呼び出しをいかに簡潔にするかを実証しました。堅牢化に向けたエラーハンドリング、設定の外部化、関数化の重要性を強調しました。</li>
<li><strong>共通の考慮点</strong>: APIバージョン管理、JSONのエスケープ処理、APIキーの安全な管理、そしてプロンプト設計の重要性を繰り返し述べました。</li>
</ul>
<p>最小実装から始め、堅牢なシステムへと発展させる過程で、各言語の特性とREST APIの原則を理解することが不可欠です。これらの知見が、皆様の業務効率化と新たな価値創造の一助となれば幸いです。</p>
<h3 class="wp-block-heading">運用チェックリスト</h3>
<p>Azure OpenAI API連携を本番運用する前に、以下の項目を確認しましょう。</p>
<ul class="wp-block-list">
<li>[ ] <strong>APIキーのセキュリティ</strong>: コードにAPIキーが直書きされていないか?環境変数、Azure Key Vault、またはセキュアな設定ファイルから取得しているか?</li>
<li>[ ] <strong>エラーハンドリング</strong>: API呼び出し失敗時(4xx, 5xxエラー、タイムアウト)の適切な処理(リトライ、ログ記録、ユーザー通知)が実装されているか?</li>
<li>[ ] <strong>レート制限対策</strong>: <code>429 Too Many Requests</code> エラー発生時に、指数バックオフなどのリトライ戦略が組み込まれているか?</li>
<li>[ ] <strong>ログ出力</strong>: リクエスト/レスポンスの詳細、エラー情報、使用トークン数などがログとして記録され、監視可能か?</li>
<li>[ ] <strong>プロンプト管理</strong>: プロンプトがバージョン管理され、変更履歴が追えるようになっているか?</li>
<li>[ ] <strong>コスト監視</strong>: Azure PortalでAPI利用量とコストを定期的に監視する仕組みがあるか?予期せぬ高額請求を避けるため、予算アラートを設定しているか?</li>
<li>[ ] <strong>APIバージョン固定</strong>: 利用しているAPIバージョンを固定し、将来的なAPI変更による影響を最小限に抑えているか?</li>
<li>[ ] <strong>プロキシ設定</strong>: 組織のネットワーク環境でプロキシ経由の通信が必要な場合、VBA/PowerShellから適切に設定されているか?</li>
<li>[ ] <strong>タイムアウト設定</strong>: ネットワーク遅延やAPIの応答遅延に備え、適切なタイムアウトが設定されているか?</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/reference#chat-completions">Azure OpenAI Service のチャット完了リファレンス</a></li>
<li><a href="https://github.com/VBA-tools/VBA-JSON-parser">VBA JSON Parser (GitHub)</a> (VBAにおけるJSON解析の堅牢化に強く推奨されるライブラリ)</li>
</ul>
VBA/PowerShellからAzure OpenAI API連携の深奥:RESTの極意と堅牢化
導入(問題設定)
日々大量のデータと格闘し、定型業務に追われるビジネスパーソンにとって、Microsoft OfficeアプリケーションとWindows環境は作業の中心です。しかし、近年の生成AI技術の発展は、これらのデスクトップ環境での作業に変革をもたらす可能性を秘めています。特にAzure OpenAI Serviceは、企業のセキュリティ要件を満たしつつ、GPTモデルの強力な能力を内部業務に組み込む道を開きます。
本稿では、皆様が慣れ親しんだVBA(Excel, Accessなど)とPowerShellから、Azure OpenAI ServiceのREST APIを直接叩き、その知能を最大限に活用するための深掘りした知識を提供します。単なるHowToに留まらず、APIの内部動作、境界条件、潜在的な落とし穴まで踏み込み、最小実装から堅牢なシステム構築への道を段階的に示します。外部ライブラリを極力排し、標準機能のみでどこまでできるかを追求することで、環境依存性を最小限に抑え、理解を深めることを目指します。
理論の要点
Azure OpenAI Serviceへの連携は、HTTPリクエストを介したREST API呼び出しが基本です。主要な概念を掘り下げましょう。
REST APIのエンドポイントとバージョン管理 :
Azure OpenAIのエンドポイントは https://<your-resource-name>.openai.azure.com/openai/deployments/<your-deployment-name>/chat/completions?api-version=YYYY-MM-DD
の形式を取ります。
api-version
は非常に重要です。このバージョンによってリクエスト・レスポンスの構造や利用可能な機能が大きく変わるため、常に最新かつ安定したバージョンを指定することが推奨されます。後方互換性が保証されない場合があるため、固定したバージョンを使い続ける運用も一考です。
落とし穴 : api-version
を指定しない、または古いバージョンを指定すると、エラーになったり、予期しないレスポンスが返ってくることがあります。
認証メカニズム (APIキー認証) :
Azure OpenAIでは、通常、リソースに発行されるAPIキーを用いた認証が主流です。これはHTTPヘッダ api-key
または Ocp-Apim-Subscription-Key
に含めて送信します。
内部動作 : クライアント(VBA/PowerShell)から送られたAPIキーはAzureのAPI Gatewayで検証され、リクエスト元の権限が確認されます。この検証に失敗すると 401 Unauthorized
エラーが返されます。
落とし穴 : APIキーの漏洩はセキュリティ上の致命的な問題です。コード内に直接埋め込むのは避け、環境変数や設定ファイルから読み込むなどの対策が必要です。
JSON形式のリクエストとレスポンス :
リクエストボディは Content-Type: application/json
ヘッダと共にJSON形式で送信されます。
チャットコンプリーションAPIの場合、messages
配列が核となります。各メッセージは role
(system, user, assistant) と content
を持ちます。
system
プロンプトはモデルの振る舞いを決定づける最も重要な指示です。
user
はユーザーからの入力。
assistant
はモデルからの過去の応答(対話履歴の再現に利用)。
レスポンスもJSON形式で、choices
配列の中に生成されたテキストが含まれます。
境界条件 : JSON文字列のエスケープ(例: ダブルクォート "
, 改行 \n
)は非常に重要です。特にVBAで手動でJSON文字列を構築する際は注意が必要です。
プロンプト設計の基礎 :
System Prompt : モデルの役割、性格、制約などを最初に与える最も重要な指示です。一度設定すると、その後の対話全体に影響を与えます。
User Prompt : ユーザーがモデルに何をさせたいかを具体的に指示します。明確で曖昧さのない指示が重要です。
Few-Shot Prompting : 期待する出力形式の例をいくつか示すことで、モデルの性能を向上させるテクニックです。
Chain-of-Thought Prompting : モデルに思考プロセスを段階的に出力させることで、複雑な問題に対する推論能力を高めます。
落とし穴 : 曖昧なプロンプトは期待しない、あるいは不正確な出力を招きます。また、プロンプトインジェクションへの対策も考慮する必要があります。
API仕様(Chat Completions API主要引数)
引数名
型
必須/任意
説明
messages
Array
必須
対話の履歴。各要素は {"role": "...", "content": "..."}
形式。role
は “system”, “user”, “assistant”。
temperature
Number
任意
生成されるテキストのランダム性(0.0~2.0)。0.0で決定的、2.0で創造的。デフォルトは1.0。
max_tokens
Integer
任意
生成されるトークンの最大数。入力プロンプトと出力の両方で合計トークン数がモデルの最大コンテキスト長を超えないこと。
top_p
Number
任意
temperatureの代替。累積確率がtop_p
に達するまでのトークンのみを考慮する(0.0~1.0)。デフォルトは1.0。
stop
Array
任意
生成を停止する文字列の配列。最大4つ。
stream
Boolean
任意
部分的なメッセージをストリーミングするかどうか。true
の場合、応答はServer-Sent Events形式になる。
seed
Integer
任意
再現可能な結果を得るためのシード値。temperature
とtop_p
に依存し、常に同じ出力を保証するものではない。
処理フロー概要
graph TD
A["VBA/PowerShellスクリプト開始"] --> B{"設定読み込み: エンドポイント, APIキー"};
B --> C["JSONリクエストボディ構築"];
C --> D{"HTTPヘッダ設定: Content-Type, api-key"};
D --> E["HTTPリクエスト送信"];
E --> F{"HTTPレスポンス受信"};
F -- 成功 (Status 2xx) --> G["JSONレスポンス解析"];
G --> H["結果利用"];
F -- 失敗 (Status 4xx/5xx) --> I["エラー処理"];
H --> J["処理完了"];
I --> J;
実装(最小→堅牢化)
ここではVBAとPowerShellそれぞれの実装を、最小構成から堅牢化へと段階的に進めます。
環境設定(共通)
Azure OpenAI Serviceのリソース作成とモデルのデプロイは完了しているものとします。
以下の情報を準備してください。
Azure OpenAI エンドポイント : https://YOUR_RESOURCE_NAME.openai.azure.com
API キー : Azure OpenAIリソースの「キーとエンドポイント」から取得
デプロイ名 : デプロイしたモデルの名前(例: gpt-35-turbo-0125
)
API バージョン : 2024-02-15
など
1. PowerShellによる最小実装
PowerShellはInvoke-RestMethod
コマンドレットにより、REST API呼び出しが非常に簡潔に行えます。JSONのシリアライズ・デシリアライズも自動で行われます。
# region --- 設定値 (本番では環境変数等から読み込むべき) ---
$azureOpenAiEndpoint = "https://YOUR_RESOURCE_NAME.openai.azure.com"
$apiKey = "YOUR_API_KEY" # !!! 本番では環境変数やAzure Key Vaultから取得すること !!!
$deploymentName = "YOUR_DEPLOYMENT_NAME"
$apiVersion = "2024-02-15"
# endregion
# Chat Completions APIのエンドポイントURLを構築
$uri = "$azureOpenAiEndpoint/openai/deployments/$deploymentName/chat/completions?api-version=$apiVersion"
# リクエストヘッダ
$headers = @{
"Content-Type" = "application/json"
"api-key" = $apiKey
}
# リクエストボディ (JSON形式)
# messages配列で会話履歴を渡す
$body = @{
messages = @(
@{ role = "system"; content = "あなたは親切なAIアシスタントです。" },
@{ role = "user"; content = "日本の首都はどこですか?" }
)
temperature = 0.7
max_tokens = 60
} | ConvertTo-Json -Depth 4 # -Depthでネストされたオブジェクトも適切にJSON化
Write-Host "URI: $uri"
Write-Host "Headers: $($headers | ConvertTo-Json)"
Write-Host "Body: $($body | ConvertTo-Json)"
try {
# Invoke-RestMethodでAPIを呼び出し、自動でJSONをPowerShellオブジェクトに変換
$response = Invoke-RestMethod -Uri $uri -Headers $headers -Body ($body | ConvertTo-Json) -Method Post
Write-Host "`n--- API Response ---"
# 応答からメッセージの内容を抽出
if ($response.choices -and $response.choices.Count -gt 0) {
$responseText = $response.choices[0].message.content
Write-Host "AIの応答: $responseText"
Write-Host "使用トークン数: $($response.usage.total_tokens)"
} else {
Write-Warning "API応答にchoicesが見つかりませんでした。"
$response | ConvertTo-Json -Depth 4 | Write-Host
}
}
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 "HTTP Status Code: $($_.Exception.Response.StatusCode)"
Write-Error "詳細エラー: $responseBody"
}
}
2. VBAによる最小実装
VBAではWinHttpRequest
オブジェクトを使用します。JSONの構築は手動または簡単な文字列連結で行う必要があります。
' このコードは標準モジュールに記述してください
#If VBA7 Then
' 64bit/32bit VBA対応のため、LongPtrはWinHttpRequestのGetObject呼び出しで不要
' 但し、WinAPI Declareステートメントを利用する場合はPtrSafeとLongPtrが必須
Private Const WHTTP_SET_REQUEST_HEADER As String = "setRequestHeader"
Private Const WHTTP_SEND As String = "send"
#Else
' VBA6以前 (32bit)
Private Const WHTTP_SET_REQUEST_HEADER As String = "setRequestHeader"
Private Const WHTTP_SEND As String = "send"
#End If
Sub CallAzureOpenAIChatMinimal()
Dim WinHttpReq As Object
Dim sURL As String
Dim sAPIKey As String
Dim sDeploymentName As String
Dim sAPIVersion As String
Dim sRequestBody As String
Dim sResponseText As String
Dim sJsonResponse As String
Dim lStatusCode As Long
Dim iPos As Long ' JSON解析用
' --- 設定値 (本番では安全な方法で管理すべき) ---
Const AZURE_OPENAI_ENDPOINT As String = "https://YOUR_RESOURCE_NAME.openai.azure.com"
sAPIKey = "YOUR_API_KEY" ' !!! 本番では環境変数や設定ファイルから取得すること !!!
sDeploymentName = "YOUR_DEPLOYMENT_NAME"
sAPIVersion = "2024-02-15"
' ---
' WinHttpRequestオブジェクトの作成
Set WinHttpReq = CreateObject("WinHttp.WinHttpRequest.5.1")
' URLの構築
sURL = AZURE_OPENAI_ENDPOINT & "/openai/deployments/" & sDeploymentName & "/chat/completions?api-version=" & sAPIVersion
' リクエストボディ (JSON形式) の構築
' VBAではJSONライブラリなしで複雑なJSONを扱うのは困難。ここでは最小限の構造で手動構築。
sRequestBody = "{""messages"": [{""role"": ""system"", ""content"": ""あなたは親切なAIアシスタントです。""}," & _
"{""role"": ""user"", ""content"": ""VBAでできることは何ですか?""}]," & _
"""temperature"": 0.7," & _
"""max_tokens"": 100}"
Debug.Print "URL: " & sURL
Debug.Print "Request Body: " & sRequestBody
' HTTPリクエストのオープンと送信
With WinHttpReq
.Open "POST", sURL, False ' Falseは同期実行を意味する
.setRequestHeader "Content-Type", "application/json"
.setRequestHeader "api-key", sAPIKey
.send sRequestBody
lStatusCode = .Status
sJsonResponse = .ResponseText
Debug.Print "HTTP Status Code: " & lStatusCode
Debug.Print "Response JSON: " & sJsonResponse
If lStatusCode >= 200 And lStatusCode < 300 Then
' 成功レスポンスの処理
' VBA標準機能でのJSON解析は非常に脆弱かつ限定的です。
' 実運用ではJSONパーサーライブラリ (例: JsonConverter.bas) の導入を強く推奨します。
' ここでは「content」フィールドを簡易的に抽出する例を示します。
iPos = InStr(sJsonResponse, """content"":""")
If iPos > 0 Then
sResponseText = Mid(sJsonResponse, iPos + Len("""content"":"""))
iPos = InStr(sResponseText, """") ' 次のダブルクォートまで
If iPos > 0 Then
sResponseText = Left(sResponseText, iPos - 1)
' JSONエスケープ文字の簡易的なデコード (例: \n -> Chr(10))
sResponseText = Replace(sResponseText, "\n", Chr(10))
sResponseText = Replace(sResponseText, "\r", Chr(13))
sResponseText = Replace(sResponseText, "\t", Chr(9))
sResponseText = Replace(sResponseText, "\""", """") ' エスケープされたダブルクォート
MsgBox "AIの応答:" & vbCrLf & sResponseText, vbInformation, "Azure OpenAI Response"
Else
MsgBox "応答JSONからcontentの終了が見つかりません。", vbCritical
End If
Else
MsgBox "応答JSONにcontentが見つかりません。JSON全体を確認してください。", vbCritical
End If
Else
' エラーレスポンスの処理
MsgBox "API呼び出しでエラーが発生しました。HTTP Status: " & lStatusCode & vbCrLf & _
"詳細: " & sJsonResponse, vbCritical, "API Error"
End If
End With
Set WinHttpReq = Nothing
End Sub
VBAにおける64bit対応とPtrSafe/LongPtrについて :
WinHttpRequest.5.1
はCOMオブジェクトであり、CreateObject
でインスタンス化されるため、直接的なDeclare PtrSafe
の要件はありません。ただし、もしVBAから直接Windows APIを呼び出すようなコード(例: レジストリ操作、ファイルパスの取得など)を追加する場合は、Declare
ステートメントにPtrSafe
キーワードを付与し、ポインタを扱う変数にはLongPtr
型を使用する必要があります。本稿のAPI連携においては、WinHttpRequestオブジェクトの使用に限定しているため、PtrSafe
やLongPtr
の直接的な使用は不要です。しかし、これがVBA開発における64bit互換性の重要な考慮点であることは明記しておきます。
3. PowerShellによる堅牢化
エラー処理の強化、設定の外部化、ログ出力などを追加します。
# region --- 設定値 (本番ではセキュアな方法で外部化すべき) ---
# PowerShellの環境変数から取得する例
$azureOpenAiEndpoint = $env:AZURE_OPENAI_ENDPOINT
$apiKey = $env:AZURE_OPENAI_API_KEY # 環境変数 'AZURE_OPENAI_API_KEY' にAPIキーを設定
$deploymentName = $env:AZURE_OPENAI_DEPLOYMENT_NAME
$apiVersion = $env:AZURE_OPENAI_API_VERSION -replace '\s','' # 空白除去
# 設定値のバリデーション
if (-not $azureOpenAiEndpoint -or -not $apiKey -or -not $deploymentName -or -not $apiVersion) {
Write-Error "環境変数が不足しています。AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_API_KEY, AZURE_OPENAI_DEPLOYMENT_NAME, AZURE_OPENAI_API_VERSION を設定してください。"
exit 1
}
# endregion
Function Get-OpenAIChatCompletion {
param(
[string]$SystemPrompt,
[string]$UserPrompt,
[double]$Temperature = 0.7,
[int]$MaxTokens = 200
)
$uri = "$azureOpenAiEndpoint/openai/deployments/$deploymentName/chat/completions?api-version=$apiVersion"
$headers = @{
"Content-Type" = "application/json"
"api-key" = $apiKey
}
$messages = @(
@{ role = "system"; content = $SystemPrompt }
)
if ($UserPrompt) {
$messages += @{ role = "user"; content = $UserPrompt }
}
$body = @{
messages = $messages
temperature = $Temperature
max_tokens = $MaxTokens
}
Write-Verbose "URI: $uri" -Verbose
Write-Verbose "Request Body: $($body | ConvertTo-Json -Depth 4)" -Verbose
try {
$response = Invoke-RestMethod -Uri $uri -Headers $headers -Body ($body | ConvertTo-Json -Depth 4) -Method Post -TimeoutSec 60
if ($response.choices -and $response.choices.Count -gt 0) {
$responseText = $response.choices[0].message.content
Write-Verbose "Total Tokens: $($response.usage.total_tokens)" -Verbose
return $responseText
} else {
Write-Warning "API応答にchoicesが見つかりませんでした。応答: $($response | ConvertTo-Json -Depth 4)"
return $null
}
}
catch {
$errorMessage = "API呼び出し中にエラーが発生しました: $($_.Exception.Message)"
Write-Error $errorMessage
if ($_.Exception.Response) {
$errorStream = $_.Exception.Response.GetResponseStream()
$reader = New-Object System.IO.StreamReader($errorStream)
$responseBody = $reader.ReadToEnd()
Write-Error "HTTP Status Code: $($_.Exception.Response.StatusCode)"
Write-Error "詳細エラー: $responseBody"
}
throw $_.Exception # 呼び出し元でエラーを捕捉できるよう再スロー
}
}
# 関数の利用例
try {
$systemPrompt = "あなたはユーザーの質問に簡潔に答えるAIです。敬語を使いましょう。"
$userQuestion = "PowerShellでREST APIを呼び出す利点は何ですか?"
$aiResponse = Get-OpenAIChatCompletion -SystemPrompt $systemPrompt -UserPrompt $userQuestion -MaxTokens 150
if ($aiResponse) {
Write-Host "`n--- AIからの回答 ---"
Write-Host $aiResponse
} else {
Write-Host "`nAIからの回答が得られませんでした。"
}
}
catch {
Write-Host "`nスクリプト実行中に致命的なエラーが発生しました。"
Write-Host "エラーメッセージ: $($_.Exception.Message)"
}
4. VBAによる堅牢化
エラー処理、設定の外部化(ここではコメントで言及)、JSON解析の課題解決(外部ライブラリ導入推奨を明記)などを考慮します。
' 標準モジュールに記述
' --- JSONパーサーライブラリの取り扱いについて ---
' VBAで堅牢にJSONを扱うには、専用のパーサーライブラリ導入が必須です。
' 例: https://github.com/VBA-tools/VBA-JSON-parser (JsonConverter.bas)
' 以下のコードでは、ライブラリが導入されていることを前提とした理想的な呼び出し例を示します。
' 実際のコードでは、上記ライブラリの JsonConverter.bas ファイルをプロジェクトにインポートしてください。
' その後、ツール -> 参照設定 で "Microsoft Scripting Runtime" を有効にする必要がある場合があります。
' ----------------------------------------------------
#If VBA7 Then
' 64bit/32bit VBA対応
' WinHttpRequestオブジェクトはCOMオブジェクトのため、PtrSafeは直接関係しない
' 但し、WinHttpRequestオブジェクトの呼び出しは、COMインターフェースを介するため、
' アドレス空間の差はCOMレイヤーが吸収します。
#Else
' VBA6以前 (32bit)
#End If
' 設定値を保持するグローバル変数またはPublic定数
' 本番環境ではINIファイル、レジストリ、またはDBから読み込む仕組みを推奨
Private Const AZURE_OPENAI_ENDPOINT As String = "https://YOUR_RESOURCE_NAME.openai.azure.com"
Private Const AZURE_OPENAI_API_KEY As String = "YOUR_API_KEY" ' !!! 環境変数などから安全に取得すべき !!!
Private Const AZURE_OPENAI_DEPLOYMENT_NAME As String = "YOUR_DEPLOYMENT_NAME"
Private Const AZURE_OPENAI_API_VERSION As String = "2024-02-15"
' 例外処理をラップした関数
Public Function GetAzureOpenAIChatCompletion(ByVal sSystemPrompt As String, _
ByVal sUserPrompt As String, _
Optional ByVal dTemperature As Double = 0.7, _
Optional ByVal lMaxTokens As Long = 200) As String
Dim WinHttpReq As Object
Dim sURL As String
Dim sRequestBody As String
Dim sJsonResponse As String
Dim lStatusCode As Long
Dim vParsedJson As Variant ' JsonConverterライブラリ用の変数
Dim sResponseContent As String
' エラーハンドリング
On Error GoTo ErrorHandler
Set WinHttpReq = CreateObject("WinHttp.WinHttpRequest.5.1")
sURL = AZURE_OPENAI_ENDPOINT & "/openai/deployments/" & AZURE_OPENAI_DEPLOYMENT_NAME & "/chat/completions?api-version=" & AZURE_OPENAI_API_VERSION
' JSON Request Body の構築 (JsonConverterライブラリ利用を想定)
' ライブラリを使用しない場合は、手動で文字列を連結するロジックが必要です (最小実装を参照)
Dim JsonMessages As Object
Set JsonMessages = CreateObject("Scripting.Dictionary") ' For JsonConverter
Dim SystemMsg As Object
Set SystemMsg = CreateObject("Scripting.Dictionary")
SystemMsg("role") = "system"
SystemMsg("content") = sSystemPrompt
JsonMessages.Add 1, SystemMsg
Dim UserMsg As Object
Set UserMsg = CreateObject("Scripting.Dictionary")
UserMsg("role") = "user"
UserMsg("content") = sUserPrompt
JsonMessages.Add 2, UserMsg
Dim RequestDict As Object
Set RequestDict = CreateObject("Scripting.Dictionary")
RequestDict("messages") = JsonMessages.Items
RequestDict("temperature") = dTemperature
RequestDict("max_tokens") = lMaxTokens
' JsonConverter.bas が存在する場合
sRequestBody = JsonConverter.ConvertToJson(RequestDict, 2) ' 2はインデントレベル
Debug.Print "URL: " & sURL
Debug.Print "Request Body: " & sRequestBody
With WinHttpReq
.Open "POST", sURL, False
.setRequestHeader "Content-Type", "application/json"
.setRequestHeader "api-key", AZURE_OPENAI_API_KEY
.send sRequestBody
lStatusCode = .Status
sJsonResponse = .ResponseText
Debug.Print "HTTP Status Code: " & lStatusCode
Debug.Print "Response JSON: " & sJsonResponse
If lStatusCode >= 200 And lStatusCode < 300 Then
' JSON Response の解析 (JsonConverterライブラリ利用を想定)
Set vParsedJson = JsonConverter.ParseJson(sJsonResponse)
If Not vParsedJson Is Nothing Then
If vParsedJson.Exists("choices") And vParsedJson("choices").Count > 0 Then
sResponseContent = vParsedJson("choices")(1)("message")("content")
GetAzureOpenAIChatCompletion = sResponseContent
Exit Function ' 正常終了
Else
Err.Raise 9999, "GetAzureOpenAIChatCompletion", "API応答に'choices'またはコンテンツが見つかりません。"
End If
Else
Err.Raise 9999, "GetAzureOpenAIChatCompletion", "API応答JSONの解析に失敗しました。"
End If
Else
Err.Raise 9999, "GetAzureOpenAIChatCompletion", "API呼び出しでエラーが発生しました。Status: " & lStatusCode & ", 詳細: " & sJsonResponse
End If
End With
CleanUp:
Set WinHttpReq = Nothing
Set JsonMessages = Nothing
Set SystemMsg = Nothing
Set UserMsg = Nothing
Set RequestDict = Nothing
Exit Function
ErrorHandler:
MsgBox "エラー発生: " & Err.Description & vbCrLf & "発生元: " & Err.Source, vbCritical
GetAzureOpenAIChatCompletion = "" ' エラー時は空文字列を返す
Resume CleanUp ' クリーンアップ処理へ
End Function
' 呼び出し例 (別のプロシージャから)
Sub TestAzureOpenAIChatRobust()
Dim sResult As String
sResult = GetAzureOpenAIChatCompletion( _
"あなたはプロの技術ブログ著者です。", _
"Excel VBAでHTTPリクエストを送る方法について、特にWinHttpRequestオブジェクトの使い方と注意点を解説してください。" _
)
If sResult <> "" Then
MsgBox "AIの生成結果:" & vbCrLf & sResult, vbInformation, "AI生成結果"
Else
MsgBox "AIからの応答を取得できませんでした。", vbCritical
End If
End Sub
ベンチ/検証
API連携の性能評価や安定性検証は、本番運用において不可欠です。
計測方法とテスト観点 :
レイテンシ計測 :
APIリクエスト送信直前とレスポンス受信直後にタイムスタンプを記録し、差分を計測します。
VBA: Timer
関数、GetTickCount
(Windows API)
PowerShell: Measure-Command
、[System.Diagnostics.Stopwatch]::StartNew()
同一のプロンプトで複数回(例: 100回)実行し、平均値、最小値、最大値、標準偏差を記録します。
ネットワーク状況やAzure OpenAI Serviceの負荷により変動するため、様々な時間帯で計測します。
成功/失敗率 :
多数のリクエストを送信し、HTTPステータスコードが2xx系で成功した割合を計測します。
4xx(クライアントエラー)や5xx(サーバーエラー)が頻発する場合、コードやAPI設定の見直しが必要です。
スループット :
一定時間内(例: 1分間)に処理できたリクエスト数を計測します。
Azure OpenAI Serviceにはレート制限(Rate Limit)があるため、これを意識したテストが必要です。制限に達すると 429 Too Many Requests
が返されます。
レスポンス内容の妥当性 :
生成されたテキストがプロンプトの意図に沿っているか、不適切な内容を含んでいないかを評価します。これは自動化が難しい部分ですが、手動での確認や、簡単なキーワードマッチングなどでの自動チェックを組み合わせます。
特にシステムプロンプトの変更が、出力にどのような影響を与えるかを重点的に検証します。
これらの検証を通じて、API連携のボトルネックを特定し、パフォーマンスチューニングやエラーハンドリングの改善に繋げます。
応用例/代替案
応用例
Excel/Accessでのデータ分析・報告書作成支援 :
Excelの特定セル範囲のデータ(例: 顧客からの自由記述アンケート)をプロンプトとして送信し、要約やカテゴリ分類、センチメント分析をAPIに実行させ、結果を別のセルに書き出す。
Accessのレポート作成時に、データベース内のテキストデータからAIに自動でコメントや洞察を生成させる。
社内チャットボット/ナレッジベース :
PowerShellスクリプトを定期実行し、社内ドキュメントやFAQを要約・整理し、SharePointリストやTeamsチャネルに自動投稿。ユーザーからの質問をAIが回答する仕組みを構築。
定型文生成とメール作成アシスト :
VBAでOutlookを操作し、メールの件名や本文、添付ファイルの内容に基づき、AIに返信文のドラフトを生成させる。
コード生成/レビューアシスト (開発者向け) :
VBAコードの一部をAPIに送信し、改善提案やバグの可能性を指摘させる。
代替案
Azure Functions/Logic Apps経由 :
VBA/PowerShellから直接Azure OpenAIを叩くのではなく、間にAzure FunctionsやLogic Appsを挟む構成です。
利点 : APIキーの直接露出を防ぎ、認証・認可をAzureプラットフォームに委ねられる。レート制限やエラーハンドリング、ログ出力などを中央集権的に管理できる。負荷分散やスケーラビリティも向上。複雑な前処理・後処理ロジックをAzure上で実行できる。
欠点 : 追加のAzureリソースコストと管理の手間が発生。レイテンシが増加する可能性。
専用SDKの利用 :
PythonやC#などの言語では、Microsoftが提供する公式SDK(azure-openai
など)があります。
利点 : REST APIの詳細を意識せずに、オブジェクト指向で直感的にAPIを利用できる。認証やエラーハンドリングもSDKがラップしてくれるため開発が容易。
欠点 : VBAやPowerShellには公式SDKがないため、本稿で扱ったようなHTTPクライアントによる直接呼び出しが必要。
これらの代替案は、システムの規模、セキュリティ要件、運用体制によって選択すべきアプローチが変わります。デスクトップアプリケーションからの手軽な利用では直接連携が適していますが、本格的な業務システムへの組み込みではAzure Functions経由がより堅牢な選択肢となります。
まとめ
本稿では、VBAおよびPowerShellからAzure OpenAI ServiceのREST APIを連携させる具体的な方法について、その内部動作や堅牢化のポイントを交えながら深掘りしました。
VBA : WinHttpRequest
オブジェクトを用いたHTTPリクエストの構築、APIキー認証、JSON文字列の手動構築とその脆弱性、そしてJSONパーサーライブラリの必要性を解説しました。64bit対応におけるPtrSafe
やLongPtr
の概念にも触れ、WinHttpRequest
自体はCOM経由のため直接の影響は少ないが、一般的なVBA開発では注意が必要であることを示しました。
PowerShell : Invoke-RestMethod
コマンドレットがJSONの自動処理を含め、REST API呼び出しをいかに簡潔にするかを実証しました。堅牢化に向けたエラーハンドリング、設定の外部化、関数化の重要性を強調しました。
共通の考慮点 : APIバージョン管理、JSONのエスケープ処理、APIキーの安全な管理、そしてプロンプト設計の重要性を繰り返し述べました。
最小実装から始め、堅牢なシステムへと発展させる過程で、各言語の特性とREST APIの原則を理解することが不可欠です。これらの知見が、皆様の業務効率化と新たな価値創造の一助となれば幸いです。
運用チェックリスト
Azure OpenAI API連携を本番運用する前に、以下の項目を確認しましょう。
[ ] APIキーのセキュリティ : コードにAPIキーが直書きされていないか?環境変数、Azure Key Vault、またはセキュアな設定ファイルから取得しているか?
[ ] エラーハンドリング : API呼び出し失敗時(4xx, 5xxエラー、タイムアウト)の適切な処理(リトライ、ログ記録、ユーザー通知)が実装されているか?
[ ] レート制限対策 : 429 Too Many Requests
エラー発生時に、指数バックオフなどのリトライ戦略が組み込まれているか?
[ ] ログ出力 : リクエスト/レスポンスの詳細、エラー情報、使用トークン数などがログとして記録され、監視可能か?
[ ] プロンプト管理 : プロンプトがバージョン管理され、変更履歴が追えるようになっているか?
[ ] コスト監視 : Azure PortalでAPI利用量とコストを定期的に監視する仕組みがあるか?予期せぬ高額請求を避けるため、予算アラートを設定しているか?
[ ] APIバージョン固定 : 利用しているAPIバージョンを固定し、将来的なAPI変更による影響を最小限に抑えているか?
[ ] プロキシ設定 : 組織のネットワーク環境でプロキシ経由の通信が必要な場合、VBA/PowerShellから適切に設定されているか?
[ ] タイムアウト設定 : ネットワーク遅延やAPIの応答遅延に備え、適切なタイムアウトが設定されているか?
参考リンク
コメント