VBA/PowerShellからAzure OpenAI APIを操る:深層まで踏み込む連携術
導入(問題設定)
Microsoft Office製品群の中核を担うVBA、そしてシステム管理や自動化でその真価を発揮するPowerShellは、今なお多くのビジネス現場で現役のツールです。しかし、現代のデータドリブンな業務プロセスにおいて、これらのレガシーと目されがちな環境から最新のAI技術、特にAzure OpenAI Service (AOAI) の力を借りられないか、という課題意識を持つ方は少なくないでしょう。
「ExcelのマクロからAIによるテキスト要約や分類をしたい」「PowerShellスクリプトで自動生成されたログをAIで解析したい」――そういったニーズは高まる一方です。本稿では、VBAおよびPowerShellという、一見するとAI連携とは縁遠い環境から、Azure OpenAI ServiceのREST APIを直接叩き、その強力な自然言語処理能力を最大限に引き出す方法を、表層的なHowToに留まらず、内部動作、境界条件、落とし穴、そして堅牢化の戦略まで、マニアックに深掘りしていきます。
理論の要点
Azure OpenAI Serviceへの連携は、HTTPプロトコルに基づいたREST APIを介して行われます。クライアント(VBA/PowerShellスクリプト)は、所定のエンドポイントに対してHTTPリクエストを送信し、JSON形式で構成されたレスポンスを受信・解析します。
REST APIの基本原則とAOAIの特有仕様
- エンドポイント: AOAIのエンドポイントURLは、Azureリソース名、デプロイメント名、APIバージョンによって一意に決まります。
例:
https://{resource_name}.openai.azure.com/openai/deployments/{deployment_id}/chat/completions?api-version={api_version}api-versionは非常に重要であり、省略するとAPI仕様の変更により予期せぬエラーや動作となる可能性があります。常に最新の安定版を指定しましょう。 - HTTPメソッド: テキスト生成などの場合は、データを送信するため
POSTメソッドを使用します。 - ヘッダー:
Content-Type: application/json: リクエストボディがJSON形式であることを示します。これは必須です。api-key: {YOUR_API_KEY}: Azure OpenAI Serviceへの認証情報です。Authorization: Bearer {YOUR_API_KEY}形式でも可能ですが、AOAIではapi-keyヘッダーが推奨されます。
- リクエストボディ (JSON): APIに送信するデータ本体です。特に
chat/completionsエンドポイントでは、messages配列が主要な要素となります。messages: 会話の履歴を保持する配列。各要素はrole(system,user,assistantのいずれか)とcontent(メッセージ本文)を持ちます。systemロールはAIの振る舞いを指示するプロンプト、userはユーザーからの入力、assistantはAIからの応答です。temperature: 応答のランダム性を制御します。0.0(決定的)から2.0(創造的)の範囲で設定します。max_tokens: 生成される応答の最大トークン数を指定します。
- レスポンスボディ (JSON): APIからの応答もJSON形式です。成功時には生成されたテキストや使用されたトークン数などが含まれます。エラー時にはエラーの詳細が返されます。
- HTTPステータスコード: リクエストの成否を示します。
200 OK: 成功。400 Bad Request: リクエストボディのJSON形式が不正、必須パラメータ不足など。401 Unauthorized: APIキーが不正、または存在しない。403 Forbidden: 認証されているが、リソースへのアクセス権がない。404 Not Found: エンドポイントURLが間違っている、デプロイメント名が存在しないなど。429 Too Many Requests: レート制限超過。Retry-Afterヘッダーで再試行までの秒数が示されることがあります。500 Internal Server Error: AOAI側の問題。
内部動作:HTTP通信のレイヤー
Invoke-RestMethodやWinHttpRequestのようなCOMオブジェクトは、その背後でTCP/IPソケット通信、SSL/TLSハンドシェイク、HTTPプロトコルの組み立て・解析を自動的に行っています。これらを意識せずとも利用できますが、例えばTLSバージョンに起因する接続エラー(古いOS環境など)や、ネットワークレベルでのタイムアウトは、この深層の知識がなければデバッグが困難になることがあります。
実装(最小→堅牢化)
ここでは、gpt-3.5-turboまたはgpt-4モデルを例に、チャット補完API (chat/completions) を呼び出す実装をVBAとPowerShellそれぞれで進めます。
API仕様(chat/completions)
POST /openai/deployments/{deployment_id}/chat/completions?api-version={api_version}
- リクエストヘッダー:
Content-Type:application/json(必須)api-key:{Azure OpenAI Service API Key}(必須)
- リクエストボディ (JSON):
messages: 配列 (必須)role: 文字列 (system,user,assistant) (必須)content: 文字列 (メッセージ本文) (必須)
temperature: 数値 (0.0 ~ 2.0, デフォルト1.0)max_tokens: 整数 (生成されるトークン数の上限)stop: 文字列配列 (生成を停止するシーケンス)top_p: 数値 (0.0 ~ 1.0, デフォルト1.0, temperatureと排他的)presence_penalty: 数値 (-2.0 ~ 2.0, デフォルト0.0, 既存のトピックに対するペナルティ)frequency_penalty: 数値 (-2.0 ~ 2.0, デフォルト0.0, 頻出するトークンに対するペナルティ)seed: 整数 (再現性を保証するシード値,system_fingerprintと共に)
- レスポンスボディ (JSON):
id: 文字列 (APIコールのID)object: 文字列 (chat.completion)created: 整数 (Unixタイムスタンプ)model: 文字列 (使用されたモデル名)choices: 配列index: 整数message: オブジェクトrole: 文字列 (assistant)content: 文字列 (生成されたテキスト)
finish_reason: 文字列 (stop,length,content_filterなど)
usage: オブジェクトprompt_tokens: 整数 (プロンプトに使用されたトークン数)completion_tokens: 整数 (応答に使用されたトークン数)total_tokens: 整数 (合計トークン数)
system_fingerprint: 文字列 (再現性情報,seedとセット)
PowerShellによる実装
最小実装
Invoke-RestMethodはHTTPリクエストとJSONパースを簡潔に扱えるため、最小実装に最適です。
# 設定値 (環境に応じて変更してください)
$resourceName = "your-openai-resource-name" # Azure OpenAIリソース名
$deploymentName = "your-deployment-name" # デプロイしたモデルの名前 (例: gpt-35-turbo)
$apiKey = "YOUR_AZURE_OPENAI_API_KEY" # Azure OpenAI APIキー
$apiVersion = "2024-02-15" # APIバージョン
$uri = "https://$resourceName.openai.azure.com/openai/deployments/$deploymentName/chat/completions?api-version=$apiVersion"
$headers = @{
"Content-Type" = "application/json"
"api-key" = $apiKey
}
$body = @{
messages = @(
@{ role = "system"; content = "あなたは有能なアシスタントです。" },
@{ role = "user"; content = "日本の首都はどこですか?" }
)
temperature = 0.7
max_tokens = 150
} | ConvertTo-Json -Depth 4 # -Depthでネストされたオブジェクトも適切にJSON化
Write-Host "Sending request to: $uri"
try {
$response = Invoke-RestMethod -Uri $uri -Method Post -Headers $headers -Body $body
Write-Host "Response received."
Write-Host "Generated Text: $($response.choices[0].message.content)"
Write-Host "Total Tokens: $($response.usage.total_tokens)"
}
catch {
Write-Error "API call failed: $($_.Exception.Message)"
if ($_.Exception.Response) {
$errorBody = $_.Exception.Response.GetResponseStream()
$reader = New-Object System.IO.StreamReader($errorBody)
$errorResponseText = $reader.ReadToEnd()
$reader.Close()
Write-Error "Error Details: $errorResponseText"
}
}
堅牢化とベストプラクティス
実際の運用では、APIキーのセキュアな管理、エラーハンドリング、リトライメカニズムが不可欠です。
# 設定値 (環境変数からの取得を推奨)
# $env:AZURE_OPENAI_RESOURCE_NAME, $env:AZURE_OPENAI_DEPLOYMENT_NAME, $env:AZURE_OPENAI_API_KEY を設定してください
$resourceName = $env:AZURE_OPENAI_RESOURCE_NAME
$deploymentName = $env:AZURE_OPENAI_DEPLOYMENT_NAME
$apiKey = $env:AZURE_OPENAI_API_KEY
$apiVersion = "2024-02-15"
# 設定値のバリデーション
if (-not ($resourceName -and $deploymentName -and $apiKey)) {
Write-Error "環境変数 AZURE_OPENAI_RESOURCE_NAME, AZURE_OPENAI_DEPLOYMENT_NAME, AZURE_OPENAI_API_KEY が設定されていません。"
exit 1
}
$uri = "https://$resourceName.openai.azure.com/openai/deployments/$deploymentName/chat/completions?api-version=$apiVersion"
function Invoke-AzureOpenAIChatCompletion {
param(
[Parameter(Mandatory=$true)]
[string]$SystemPrompt,
[Parameter(Mandatory=$true)]
[string]$UserPrompt,
[Parameter(Mandatory=$false)]
[int]$MaxTokens = 150,
[Parameter(Mandatory=$false)]
[double]$Temperature = 0.7,
[Parameter(Mandatory=$false)]
[int]$MaxRetries = 5,
[Parameter(Mandatory=$false)]
[int]$InitialDelaySeconds = 1
)
$headers = @{
"Content-Type" = "application/json"
"api-key" = $apiKey
}
$body = @{
messages = @(
@{ role = "system"; content = $SystemPrompt },
@{ role = "user"; content = $UserPrompt }
)
temperature = $Temperature
max_tokens = $MaxTokens
} | ConvertTo-Json -Depth 4
for ($attempt = 1; $attempt -le $MaxRetries; $attempt++) {
Write-Host "Attempt $attempt of $MaxRetries to call Azure OpenAI API..."
try {
$response = Invoke-RestMethod -Uri $uri -Method Post -Headers $headers -Body $body -ContentType "application/json" -TimeoutSec 60
return $response.choices[0].message.content
}
catch {
$statusCode = $_.Exception.Response.StatusCode.value__
$errorMessage = $_.Exception.Message
Write-Warning "API call failed (Attempt $attempt): [$statusCode] $errorMessage"
if ($_.Exception.Response) {
$errorStream = $_.Exception.Response.GetResponseStream()
$reader = New-Object System.IO.StreamReader($errorStream)
$errorResponseText = $reader.ReadToEnd()
$reader.Close()
Write-Warning "Error Details: $errorResponseText"
# レート制限 (429) の場合は Retry-After ヘッダーをチェック
if ($statusCode -eq 429 -and $_.Exception.Response.Headers['Retry-After']) {
$retryAfter = [int]$_.Exception.Response.Headers['Retry-After']
Write-Warning "Rate limit hit. Retrying after $retryAfter seconds (from server)."
Start-Sleep -Seconds $retryAfter
continue
}
}
# 4xx系のクライアントエラーはリトライしない
if ($statusCode -ge 400 -and $statusCode -lt 500 -and $statusCode -ne 429) {
Write-Error "Client-side error ($statusCode). Aborting retries."
throw "Azure OpenAI API Client Error: $errorResponseText"
}
# それ以外のエラー (主に5xx系) やネットワークエラーは指数バックオフでリトライ
if ($attempt -lt $MaxRetries) {
$delay = $InitialDelaySeconds * [math]::Pow(2, $attempt - 1)
Write-Warning "Retrying in $delay seconds..."
Start-Sleep -Seconds $delay
} else {
Write-Error "Max retries reached. Failing."
throw "Azure OpenAI API Failed after multiple retries: $errorResponseText"
}
}
}
return $null # 全てのリトライが失敗した場合
}
# 使用例
try {
$result = Invoke-AzureOpenAIChatCompletion `
-SystemPrompt "あなたは優秀なデータ分析アシスタントです。ユーザーの質問に簡潔に答えてください。" `
-UserPrompt "2023年の日本のGDP成長率は?"
if ($result) {
Write-Host "AIの回答: $result"
} else {
Write-Error "AIからの回答を取得できませんでした。"
}
}
catch {
Write-Error "スクリプト全体でエラーが発生しました: $($_.Exception.Message)"
}
VBAによる実装
VBAでは、Microsoft WinHTTP ServicesまたはMicrosoft XML, v6.0などのCOMオブジェクトを利用してHTTPリクエストを送信します。ここでは、広く利用可能なWinHttp.WinHttpRequest.5.1オブジェクト(あるいはより新しいMSXML2.ServerXMLHTTP60など)を使用します。
最小実装
' 参照設定は不要だが、IntelliSenseを有効にする場合は
' 「ツール」->「参照設定」で「Microsoft WinHTTP Services, version 5.1」にチェック
Sub CallAzureOpenAIApi_Minimal()
Dim objHTTP As Object ' WinHttp.WinHttpRequest.5.1
Dim sURL As String
Dim sAPIKey As String
Dim sRequestBody As String
Dim sResponse As String
'--- 設定値 (環境に応じて変更してください) ---
Const RESOURCE_NAME As String = "your-openai-resource-name" ' Azure OpenAIリソース名
Const DEPLOYMENT_NAME As String = "your-deployment-name" ' デプロイしたモデルの名前
sAPIKey = "YOUR_AZURE_OPENAI_API_KEY" ' Azure OpenAI APIキー
Const API_VERSION As String = "2024-02-15"
'-------------------------------------------
sURL = "https://" & RESOURCE_NAME & ".openai.azure.com/openai/deployments/" & _
DEPLOYMENT_NAME & "/chat/completions?api-version=" & API_VERSION
' JSONボディの組み立て
' VBAでJSONを直接構築するのは手間がかかるが、ここでは手動でシンプルに構成
sRequestBody = "{""messages"": [{""role"": ""system"", ""content"": ""あなたは有能なアシスタントです。""}," & _
"{""role"": ""user"", ""content"": ""日本の首都はどこですか?""}]," & _
"""temperature"": 0.7, ""max_tokens"": 150}"
Set objHTTP = CreateObject("WinHttp.WinHttpRequest.5.1") ' または "MSXML2.XMLHTTP"
With objHTTP
.Open "POST", sURL, False ' Falseで同期呼び出し
.SetRequestHeader "Content-Type", "application/json"
.SetRequestHeader "api-key", sAPIKey
.Send sRequestBody
If .Status = 200 Then
sResponse = .ResponseText
Debug.Print "Response Received: " & sResponse
' レスポンスJSONから必要な情報を抽出 (簡易版)
' 実際のアプリケーションではJSONパーサーの利用を検討
Dim jsonParts As Variant
jsonParts = Split(sResponse, """content"":""")
If UBound(jsonParts) > 0 Then
Dim contentPart As String
contentPart = Split(jsonParts(1), """")(0)
Debug.Print "Generated Text: " & contentPart
Else
Debug.Print "Content not found in response."
End If
Else
Debug.Print "API Call Failed. Status: " & .Status & ", StatusText: " & .StatusText
Debug.Print "Response Text: " & .ResponseText ' エラー詳細
End If
End With
Set objHTTP = Nothing
End Sub
堅牢化とベストプラクティス
VBAでの堅牢化は、エラーハンドリング、リトライ、タイムアウト設定、そしてクラスモジュールによる抽象化が中心になります。JSONの構築と解析には、VBA-JSONなどの外部ライブラリも選択肢に入りますが、ここでは外部ライブラリに極力依存しないという要件に沿って、文字列操作による簡易的なアプローチを示します。
VBAにおける64bit対応/PtrSafe/LongPtrについて:
VBAの64bit対応は主にDeclare FunctionでWindows API(WinAPI)を直接呼び出す際に重要になります。ポインタやハンドルなど、メモリ上のアドレスを扱う引数や戻り値の型をLongPtrに、そしてDeclareステートメントにPtrSafeキーワードを追加することが必須です。
WinHttp.WinHttpRequest.5.1のようなCOMオブジェクトを利用する場合、COMレイヤーが32bit/64bit間のアーキテクチャの違いを吸収するため、スクリプト内で直接PtrSafeやLongPtrを使用する場面は稀です。COMオブジェクトのメソッド引数にポインタを渡すような特殊なケースを除き、既存のCOMオブジェクトを利用する限りは、ほとんど意識する必要はありません。しかし、もしVBAからWinAPIを直接呼び出して高度なネットワーク操作や低レイヤー処理を行う場合は、これらのキーワードの理解と適用が不可欠です。本稿のスコープでは、COMオブジェクトによる高レベルAPI呼び出しに限定するため、PtrSafe/LongPtrの具体的なコード例は示しませんが、VBA開発者としては常にその存在と必要性を意識しておくべきです。
以下は、AOAI連携機能をクラスモジュールにカプセル化し、リトライロジックを追加した例です。
クラスモジュール cAzureOpenAIClient
' クラスモジュール名: cAzureOpenAIClient
' 参照設定: 不要 (CreateObjectを使用)
Private Const CLASS_NAME As String = "cAzureOpenAIClient"
Private pResourceName As String
Private pDeploymentName As String
Private pAPIKey As String
Private pAPIVersion As String
Private pMaxRetries As Long
Private pInitialDelaySeconds As Long
Private pRequestTimeoutSeconds As Long
'*****************************************************
' Public Properties
'*****************************************************
Public Property Let ResourceName(ByVal sName As String)
pResourceName = sName
End Property
Public Property Get ResourceName() As String
ResourceName = pResourceName
End Property
Public Property Let DeploymentName(ByVal sName As String)
pDeploymentName = sName
End Property
Public Property Get DeploymentName() As String
DeploymentName = pDeploymentName
End Property
Public Property Let APIKey(ByVal sKey As String)
pAPIKey = sKey
End Property
Public Property Get APIKey() As String
APIKey = pAPIKey
End Property
Public Property Let APIVersion(ByVal sVersion As String)
pAPIVersion = sVersion
End Property
Public Property Get APIVersion() As String
APIVersion = pAPIVersion
End Property
Public Property Let MaxRetries(ByVal lRetries As Long)
pMaxRetries = lRetries
End Property
Public Property Get MaxRetries() As Long
MaxRetries = pMaxRetries
End Property
Public Property Let InitialDelaySeconds(ByVal lDelay As Long)
pInitialDelaySeconds = lDelay
End Property
Public Property Get InitialDelaySeconds() As Long
InitialDelaySeconds = pInitialDelaySeconds
End Property
Public Property Let RequestTimeoutSeconds(ByVal lTimeout As Long)
pRequestTimeoutSeconds = lTimeout
End Property
Public Property Get RequestTimeoutSeconds() As Long
RequestTimeoutSeconds = pRequestTimeoutSeconds
End Property
'*****************************************************
' Initialize
'*****************************************************
Private Sub Class_Initialize()
' デフォルト値の設定
pAPIVersion = "2024-02-15"
pMaxRetries = 3
pInitialDelaySeconds = 2
pRequestTimeoutSeconds = 60
End Sub
'*****************************************************
' Public Methods
'*****************************************************
Public Function ChatCompletion(ByVal sSystemPrompt As String, _
ByVal sUserPrompt As String, _
Optional ByVal dTemperature As Double = 0.7, _
Optional ByVal lMaxTokens As Long = 150) As String
Dim objHTTP As Object ' WinHttp.WinHttpRequest.5.1
Dim sURL As String
Dim sRequestBody As String
Dim sResponse As String
Dim lAttempt As Long
Dim lDelay As Long
Dim lStatusCode As Long
On Error GoTo ErrorHandler
' 設定値のバリデーション
If pResourceName = "" Or pDeploymentName = "" Or pAPIKey = "" Then
Err.Raise vbObjectError + 1000, CLASS_NAME, "Required properties (ResourceName, DeploymentName, APIKey) are not set."
End If
sURL = "https://" & pResourceName & ".openai.azure.com/openai/deployments/" & _
pDeploymentName & "/chat/completions?api-version=" & pAPIVersion
' JSONボディの組み立て (簡易版、必要に応じてJSONパーサーを導入)
sRequestBody = "{""messages"": [{""role"": ""system"", ""content"": """ & EscapeJson(sSystemPrompt) & """}," & _
"{""role"": ""user"", ""content"": """ & EscapeJson(sUserPrompt) & """}]," & _
"""temperature"": " & Replace(CStr(dTemperature), ",", ".") & "," & _
"""max_tokens"": " & lMaxTokens & "}"
Set objHTTP = CreateObject("WinHttp.WinHttpRequest.5.1")
For lAttempt = 1 To pMaxRetries + 1 ' MaxRetries + 1回のリトライ試行
Debug.Print CLASS_NAME & ": Attempt " & lAttempt & " of " & (pMaxRetries + 1)
With objHTTP
.Open "POST", sURL, False ' 同期呼び出し
.SetRequestHeader "Content-Type", "application/json"
.SetRequestHeader "api-key", pAPIKey
.SetTimeouts pRequestTimeoutSeconds * 1000, pRequestTimeoutSeconds * 1000, pRequestTimeoutSeconds * 1000, pRequestTimeoutSeconds * 1000 ' resolve, connect, send, receive timeouts (ms)
On Error Resume Next ' Send中にエラーが発生する可能性もある
.Send sRequestBody
lStatusCode = .Status
sResponse = .ResponseText
On Error GoTo ErrorHandler
If lStatusCode = 200 Then
ChatCompletion = ExtractContentFromJson(sResponse)
Exit Function
Else
Debug.Print CLASS_NAME & ": API Call Failed (Attempt " & lAttempt & "). Status: " & lStatusCode & ", StatusText: " & .StatusText
Debug.Print CLASS_NAME & ": Error Response: " & sResponse
If lStatusCode >= 400 And lStatusCode < 500 And lStatusCode <> 429 Then
' クライアントサイドエラー (4xx系、レート制限429以外) はリトライしない
Err.Raise vbObjectError + 1001, CLASS_NAME, "Client-side API error: " & lStatusCode & " - " & sResponse
End If
If lAttempt <= pMaxRetries Then ' MaxRetries回まではリトライを試みる
lDelay = pInitialDelaySeconds * (2 ^ (lAttempt - 1)) ' 指数バックオフ
' Retry-Afterヘッダーがあればそちらを優先 (VBA WinHttpRequestでは直接取得がやや面倒なので簡易化)
' 実際には.GetResponseHeader("Retry-After")で取得を試みるべき
Debug.Print CLASS_NAME & ": Retrying in " & lDelay & " seconds..."
Application.Wait Now + TimeValue("00:00:" & lDelay)
DoEvents ' UIフリーズ防止
Else
' 最大リトライ回数を超えた
Err.Raise vbObjectError + 1002, CLASS_NAME, "Max retries exceeded. Last status: " & lStatusCode & " - " & sResponse
End If
End If
End With
Next lAttempt
' ここに到達した場合、エラーハンドラにジャンプしていなければnull/空文字列を返す
ChatCompletion = ""
Exit Function
ErrorHandler:
Debug.Print CLASS_NAME & ": An error occurred: " & Err.Description
ChatCompletion = "" ' エラー時は空文字列を返す
Err.Clear
End Function
' JSON文字列内の特殊文字をエスケープする簡易関数
Private Function EscapeJson(ByVal sText As String) As String
sText = Replace(sText, "\", "\\")
sText = Replace(sText, """", "\""")
sText = Replace(sText, Chr(8), "\b") ' Backspace
sText = Replace(sText, Chr(12), "\f") ' Form feed
sText = Replace(sText, Chr(10), "\n") ' Newline
sText = Replace(sText, Chr(13), "\r") ' Carriage return
sText = Replace(sText, Chr(9), "\t") ' Tab
EscapeJson = sText
End Function
' 応答JSONからcontentを抽出する簡易関数
' 堅牢な実装にはJSONパーサーの使用を推奨
Private Function ExtractContentFromJson(ByVal sJson As String) As String
Dim re As Object ' RegExp
Set re = CreateObject("VBScript.RegExp")
re.Pattern = """content"":\s*""([^""]*)"""
re.Global = False
If re.Test(sJson) Then
ExtractContentFromJson = re.Execute(sJson)(0).SubMatches(0)
Else
ExtractContentFromJson = ""
End If
Set re = Nothing
End Function
標準モジュールでの使用例
Sub TestAzureOpenAIClient()
Dim oAIClient As cAzureOpenAIClient
Dim sResult As String
'--- 環境変数から取得するなどのセキュアな方法を推奨 ---
Const AZURE_OPENAI_RESOURCE_NAME As String = "your-openai-resource-name"
Const AZURE_OPENAI_DEPLOYMENT_NAME As String = "your-deployment-name"
Const AZURE_OPENAI_API_KEY As String = "YOUR_AZURE_OPENAI_API_KEY"
'----------------------------------------------------
Set oAIClient = New cAzureOpenAIClient
With oAIClient
.ResourceName = AZURE_OPENAI_RESOURCE_NAME
.DeploymentName = AZURE_OPENAI_DEPLOYMENT_NAME
.APIKey = AZURE_OPENAI_API_KEY
.MaxRetries = 5 ' リトライ回数を設定
.InitialDelaySeconds = 3 ' 初期遅延秒数を設定
.RequestTimeoutSeconds = 90 ' リクエストタイムアウト秒数を設定
End With
On Error GoTo ErrorHandler
Debug.Print "Calling Azure OpenAI API..."
sResult = oAIClient.ChatCompletion("あなたは優秀なデータ分析アシスタントです。ユーザーの質問に簡潔に答えてください。", _
"2023年の日本のGDP成長率は?")
If sResult <> "" Then
Debug.Print "AIの回答: " & sResult
Else
Debug.Print "AIからの回答を取得できませんでした。詳細はデバッグウィンドウを確認してください。"
End If
Exit Sub
ErrorHandler:
Debug.Print "An error occurred in TestAzureOpenAIClient: " & Err.Description
MsgBox "エラーが発生しました: " & Err.Description, vbCritical
Set oAIClient = Nothing
End Sub
Azure OpenAI API連携フロー
graph TD
A["VBA/PowerShellスクリプト開始"] --> B("API設定読み込み: Resource, Deployment, Key, Version")
B --> C{"プロンプトとパラメータでリクエストボディを構築"}
C --> D("HTTPヘッダー設定: Content-Type, api-key")
D --> E("APIエンドポイントURL構築")
E --> F["HTTP POSTリクエスト送信"]
F -- ネットワークエラー/タイムアウト --> G{"リトライ判定?"}
F -- HTTPステータス 200 OK --> H("レスポンスJSON受信")
F -- HTTPステータス 4XX/5XX --> I{"エラー種別判定"}
G -- リトライ可能 --> J("指数バックオフ待機")
J --> F
G -- リトライ不可 --> K("処理失敗/エラー報告")
I -- 429 Too Many Requests --> L("Retry-Afterヘッダーチェック")
L -- 指定あり --> J
L -- 指定なし/その他リトライ可能なエラー --> J
I -- 4XXクライアントエラー(429以外) --> K
I -- 5XXサーバーエラー --> J
H --> M("レスポンスJSON解析: 生成テキスト、トークン数など抽出")
M --> N["結果の利用/表示"]
N --> O["スクリプト終了"]
K --> O
ベンチ/検証
AOAI連携コードの品質を担保するためには、単なる動作確認だけでなく、性能と堅牢性の検証が不可欠です。
- 機能テスト:
- 正常系: 短いプロンプト、長いプロンプト、複雑なプロンプトで期待通りの応答が得られるか。
- 異常系:
- 無効なAPIキー、存在しないデプロイ名、不正なJSONボディで
401/404/400エラーが正しくハンドリングされるか。 - レート制限(意図的に短時間で多数リクエストを送信)時にリトライが機能し、最終的に応答を得られるか、または適切なエラーで終了するか。
- ネットワーク切断時やDNS解決失敗時にタイムアウト・リトライが働くか。
- 無効なAPIキー、存在しないデプロイ名、不正なJSONボディで
- 性能テスト:
- 応答時間: プロンプトの長さ、
max_tokensの値、モデル(gpt-3.5-turbovsgpt-4)の違いが応答時間にどう影響するかを計測。複数回実行して平均値を算出。 - トークン消費量: 同様のプロンプトで何度か実行し、
usage.total_tokensが変動するかどうかを確認。
- 応答時間: プロンプトの長さ、
- 境界条件の確認:
max_tokensを極端に小さな値(例: 1)や大きな値に設定した場合の挙動。temperatureを0.0(決定的)や2.0(創造的)に設定した際の応答の変化。- 非常に長いプロンプト(コンテキストウィンドウの最大値に近づける)を送信した場合の動作。
計測方法の簡略例:
- VBA:
Dim StartTime As Double StartTime = Timer ' 秒単位の現在時刻 ' API呼び出しコード Dim EndTime As Double EndTime = Timer Debug.Print "処理時間: " & (EndTime - StartTime) & "秒"
- PowerShell:
$startTime = Get-Date # API呼び出しコード $endTime = Get-Date $duration = $endTime - $startTime Write-Host "処理時間: $($duration.TotalSeconds)秒"
応用例/代替案
応用例
- Excelでのレポート自動生成: VBAからAOAIを呼び出し、特定のデータセットに基づいてサマリーや洞察を生成し、Excelシートに直接書き込む。
- PowerShellによるログ分析: サーバーログやイベントログをPowerShellで収集・フィルタリングし、AOAIで異常検出やカテゴリ分類を行う。
- 社内Q&Aシステム: VBA/PowerShellスクリプトでユーザーの質問を受け付け、既存ドキュメントを参照させつつAOAIで回答を生成、Teamsなどへ通知する。
- 定型業務の自動化: 例えば、メールの件名や本文からキーワードを抽出し、それに基づいて次のアクション(タスク作成、ファイル分類など)をAIに判断させる。
代替案
- Python等の中間層の利用:
VBA/PowerShellで複雑なJSON操作や高度なエラーハンドリング、非同期処理を実装するのは困難が伴います。PythonにはOpenAIのSDKや豊富なライブラリが存在するため、VBA/PowerShellからPythonスクリプトを呼び出し、Python側でAOAI連携の全てを担わせるという方法も有効です。
- VBA:
Shell関数でPythonスクリプトを実行し、標準出力/ファイル経由で結果を受け取る。 - PowerShell:
Start-Processやpython.exeを直接呼び出す。
- VBA:
- Azure Functions/Logic Appsなどの利用: VBA/PowerShellから直接AOAIを呼び出すのではなく、Azure FunctionsやLogic Appsなどのサーバーレスサービスを間に挟むことで、認証情報の管理を一元化し、レート制限やリトライロジックをクラウド側で堅牢に実装できます。これによりVBA/PowerShell側のコードは非常にシンプルになります。
- データブリッジングツール: Power Automate DesktopのようなRPAツールを利用し、AI連携部分をGUIで設定し、PowerShellやVBAと連携させるアプローチも可能です。
まとめ
本稿では、VBAおよびPowerShellという既存環境からAzure OpenAI ServiceのREST APIを連携させる手法について、その最小実装から堅牢化、そして内部動作や落とし穴まで踏み込んで解説しました。
- VBA:
WinHttp.WinHttpRequest.5.1オブジェクトを使ったHTTP通信、簡易的なJSON構築・解析、クラスモジュールによる抽象化とエラー・リトライハンドリング。64bit環境におけるPtrSafe/LongPtrの要点についても言及しました。 - PowerShell:
Invoke-RestMethodによる簡潔なHTTPリクエスト、環境変数を用いたAPIキー管理、try/catch/finallyと指数バックオフを用いた堅牢なリトライロジック。
VBAやPowerShellは、手元のExcelファイルや既存システムとの連携において、依然として強力なツールです。これらにAzure OpenAI Serviceの力を組み合わせることで、従来手作業で行っていたテキスト処理や意思決定プロセスにAIの知見を導入し、業務の高度化・自動化を劇的に推進できる可能性を秘めています。
しかし、その実装にはAPIキーの厳重な管理、レート制限への対策、JSONの厳密な取り扱いといった細心の注意が求められます。本稿で紹介した堅牢化のポイントと運用チェックリストを参考に、セキュアで信頼性の高いAI連携システムを構築してください。

コメント