處理資料時請 ChatGPT 幫忙已成日常,就算是寫 PowerShell 時也不例外。因此,我想要有個函式,希望能做到傳 Prompt 批次呼叫 ChatGPT API 執行翻譯、摘要等的任務,隨時隨地想要 AI 就有 AI。

舉個例子,假設我有個 JSON 檔 demo.json:

[
    { "id": 1, "name_en": "Harry Potter and the Philosopher's Stone" },
    { "id": 2, "name_en": "The Bridges of Madison County"},
    { "id": 3, "name_en": "The Da Vinci Code"},
    { "id": 4, "name_en": "Anne of Green Gables"}
]

希望能用短短幾行 PowerShell 程式為 JSON 裡的英文書加上台灣翻譯書名:

. .\gpt-chat.ps1
SetSystemPrompt '你是暢銷書專家,請將以下英文書名翻譯成台灣發行書名(只需回答書名即可):'
Get-Content .\demo.json | ConvertFrom-Json | ForEach-Object {
    $item = [PSCustomObject]$_
    $name_cht = CallComplete $_.name_en
    # 迴圈呼叫時,實務上可能要加點時間間隔避免超過流量上限
    # 更有效率的做法是改成一次查詢多筆,再從回答解析取出多筆結果
    Start-Sleep -Seconds 1
    $item | Add-Member -MemberType NoteProperty -Name name_cht -Value $name_cht
    return $item
} | ConvertTo-Json

期望執行後得到以下內容:

這個 gpt-chat.ps1 要怎麼寫呢?一點也不難。

若你有 Azure OpenAI API 服務,開啟 Azure OpenAI Studio 的聊天遊樂場,裡面有個【檢視程式碼】功能:

上面有 C#、Python、JavaScript、Java、Go 的程式範例,PowerShell 的話,則可參考 curl 寫法,改寫成 Invoke-WebRequets 或 Invoke-RestMethod 即可。

實測發現 PowerShell 5 的 Invoke-RestMethod 的 UTF8 支援不佳,索性改用 WebClient 物件較好控制細節;PowerShell 6+ 則可直接用 Invoke-RestMethod。而依照慣例,我不愛 API Key 用碼存,故花了點工夫用 SecretString 加密,而首次使用會詢問 URL 跟 API Key (如下圖),參考上圖 curl 範例中的網址跟 API 金鑰填入存成 .settings JSON 檔,之後就可以愉快使用了。

以下是完整程式碼:

param ([string]$question)
$ErrorActionPreference = "Stop"
$settingsPath = '.\azure-openai.settings'
function ReadApiSettings() {
    try {
        if (Test-Path $settingsPath) {
            $settings = Get-Content $settingsPath | ConvertFrom-Json
            $apiUrl = $settings.apiUrl
            $apiKey = $settings.apiKey
        }
    }
    catch {  }
    if ([string]::IsNullOrEmpty($apiUrl) -or [string]::IsNullOrEmpty($apiKey)) {
        Write-Host "Please set Azure OpenAI url and key" -ForegroundColor Yellow
        if ([string]::IsNullOrEmpty($apiUrl)) {
            Write-Host "  ex: https://<host-name>.openai.azure.com/openai/deployments/<deploy-name>/chat/completions?api-version=2024-02-15-preview"
            Write-Host "API Url: " -ForegroundColor Cyan
            $apiUrl = Read-Host
        }
        if ([string]::IsNullOrEmpty($apiKey)) {
            Write-Host "API Key: " -ForegroundColor Cyan
            $apiKey = Read-Host -AsSecureString | ConvertFrom-SecureString
        }
        @{ 
            apiUrl = $apiUrl
            apiKey = $apiKey
        } | ConvertTo-Json | Set-Content -Path $settingsPath
    }
    $Global:apiUrl = $apiUrl
    $Global:apiKey = $apiKey
}
ReadApiSettings
$Global:systemPrompt = 'You are an AI assistant that helps people find information.'
function SetSystemPrompt($prompt) { $Global:systemPrompt = $prompt }
function DecryptApiKey() {
    $secStr = $Global:apiKey | ConvertTo-SecureString
    $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secStr)
    return [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
}
function CallComplete($prompt) {
    $fullPrompt = $Global:systemPrompt + ' ' + $prompt
    $payLoad = [PSCustomObject]@{
        messages = @( 
            [PSCustomObject]@{ 
                role = "system"
                content = @( 
                    [PSCustomObject]@{
                        type = "text"
                        text = $fullPrompt
                        temperature=0.7
                        top_p = 0.95
                        max_tokens = 2048
                    }
                )
            }
        )
    }
    $json = $payLoad | ConvertTo-Json -Depth 5

    # PowerShell 6+,可直接用 Invoke-RestMethod
    # $response = Invoke-RestMethod -Uri $Global:apiUrl -Method POST -Headers $headers -Body $json
    # PowerShell 5.1,需用 WebClient 自行處理編碼問題
    $wc = New-Object System.Net.WebClient
    $wc.Headers.Add('Content-Type', 'application/json; charset=utf-8')
    $wc.Headers.Add('api-key', (DecryptApiKey))
    $response = $wc.UploadData($Global:apiUrl,  [System.Text.Encoding]::UTF8.GetBytes($json))
    $response = [System.Text.Encoding]::UTF8.GetString($response) | ConvertFrom-Json
    return $response.choices[0].message.content 
}
if ($question -ne '') {
    CallComplete($question)
}

就醬,未來在 PowerShell 要整合 ChatGPT API 就方便多了。

Example of how to call Azure OpenAI api in PowerShell to provide chatting or translation function.


Comments

# by Hank

可以請 ChatGPT 寫出符合本文需求的 PowerShell 麼

# by Jeffrey

to Hank, 請它給 Python 範例的成功率比較高,PowerShell 的訓練資料偏少,能幫上大忙,但精準度仍不夠理想。 依我的看法,靠 AI 寫程式至少要有能力看懂程式碼,矇著眼 AI 給什麼跑什麼,出錯只能問 AI 怎麼改,很難過著幸福快樂的日子。(至少以現在 LLM 的水準還沒辦法)

# by Lou

等待複核中,留言將在稍後顯示 / The comment is awaiting review.

Post a comment