适用于 PowerShell 7.0 及以上版本
大语言模型(LLM)已经渗透到开发工作的方方面面。当我们需要在自动化脚本中集成 AI 能力时,直接调用 OpenAI 兼容的 REST API 是最灵活的方式。PowerShell 内置的 Invoke-RestMethod cmdlet 天然适合完成这项工作——无需安装额外 SDK,几行代码即可实现与 LLM 的交互。
本文将从零开始,逐步带你完成 API Key 配置、单次问答封装、多轮对话、代码审查场景以及 Token 用量估算。
准备工作:配置 API Key 调用任何 OpenAI 兼容接口都需要一个 API Key。出于安全考虑,我们通过环境变量来管理它,避免在代码中硬编码。
1 2 Add-Content -Path $PROFILE -Value '`$env:OPENAI_API_KEY = "sk-your-key-here"'
如果你使用的是国内中转代理或其他 OpenAI 兼容服务(如 Azure OpenAI、DeepSeek),还需要额外设置基础 URL:
1 2 $env:OPENAI_API_BASE = "https://your-proxy.example.com/v1"
配置完成后,重新打开 PowerShell 会话即可生效。你可以通过以下方式验证:
1 2 PS > $env :OPENAI_API_KEY sk-proj-xxxxxxxxxxxxxx
单次问答:封装通用函数 下面我们封装一个 Invoke-LLMChat 函数,它接受用户提问,返回模型的回复。这个函数是后续所有场景的基础。
函数内部会自动读取环境变量中的 API Key,将用户消息和系统提示组装成 OpenAI Chat Completion 格式的 JSON,然后通过 Invoke-RestMethod 发送 POST 请求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 function Invoke-LLMChat { param ( [Parameter (Mandatory )] [string ]$Prompt , [string ]$Model = "gpt-4o-mini" , [string ]$SystemMessage = "你是一个有帮助的 PowerShell 助手。" , [double ]$Temperature = 0.7 , [int ]$MaxTokens = 2048 ) $apiKey = $env:OPENAI_API_KEY if (-not $apiKey ) { throw "请先设置环境变量 OPENAI_API_KEY" } $baseUrl = if ($env:OPENAI_API_BASE ) { $env:OPENAI_API_BASE } else { "https://api.openai.com/v1" } $uri = "$baseUrl /chat/completions" $body = @ { model = $Model messages = @ ( @ { role = "system" ; content = $SystemMessage } @ { role = "user" ; content = $Prompt } ) temperature = $Temperature max_tokens = $MaxTokens } | ConvertTo-Json -Depth 5 -Compress $response = Invoke-RestMethod ` -Uri $uri ` -Method Post ` -Headers @ { Authorization = "Bearer $apiKey " } ` -ContentType "application/json; charset=utf-8" ` -Body ([System.Text.Encoding ]::UTF8.GetBytes($body )) return $response .choices[0 ].message.content }
这里有两个值得注意的细节:一是用 -Compress 参数减少 JSON 体积(去掉多余空白),二是用 UTF8.GetBytes 确保中文字符不会在传输中变成乱码。如果你不需要自定义端点,也可以省略 $env:OPENAI_API_BASE 相关逻辑。
调用示例:
1 2 3 4 PS > Invoke-LLMChat -Prompt "用一行 PowerShell 代码获取本机所有 IP 地址" 你可以使用以下命令获取本机所有 IP 地址: (Get-NetIPAddress -AddressFamily IPv4).IPAddress
多轮对话:维护上下文历史 单次问答没有”记忆”。要让模型理解上下文,我们需要把完整的对话历史(包括之前的用户消息和助手回复)都发给 API。下面这个函数实现了一个交互式的多轮对话循环。
关键点在于 $messages 数组——每次用户发言后追加一条 user 消息,收到模型回复后追加一条 assistant 消息,这样上下文就在数组中不断累积。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 function Start-LLMConversation { param ( [string ]$Model = "gpt-4o-mini" ) $apiKey = $env:OPENAI_API_KEY $baseUrl = if ($env:OPENAI_API_BASE ) { $env:OPENAI_API_BASE } else { "https://api.openai.com/v1" } $uri = "$baseUrl /chat/completions" $messages = @ ( @ { role = "system" ; content = "你是一个有帮助的助手,请用中文回答。" } ) Write-Host "多轮对话已启动,输入 'exit' 退出" -ForegroundColor Cyan while ($true ) { $userInput = Read-Host "你" if ($userInput -eq "exit" ) { break } if ([string ]::IsNullOrWhiteSpace($userInput )) { continue } $messages += @ { role = "user" ; content = $userInput } $body = @ { model = $Model messages = $messages } | ConvertTo-Json -Depth 10 -Compress $response = Invoke-RestMethod ` -Uri $uri ` -Method Post ` -Headers @ { Authorization = "Bearer $apiKey " } ` -ContentType "application/json; charset=utf-8" ` -Body ([System.Text.Encoding ]::UTF8.GetBytes($body )) $assistantMessage = $response .choices[0 ].message.content $messages += @ { role = "assistant" ; content = $assistantMessage } Write-Host "`n助手: $assistantMessage `n" -ForegroundColor Green $usage = $response .usage Write-Host "[Token] 输入: $ ($usage .prompt_tokens) | 输出: $ ($usage .completion_tokens)" -ForegroundColor DarkGray } }
运行效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 PS> Start-LLMConversation 多轮对话已启动,输入 'exit' 退出 你: 写一个函数检查磁盘空间 助手: 这是一个检查磁盘空间的函数: function Get-DiskSpace { param([string]$ComputerName = $env :COMPUTERNAME) Get-CimInstance -ClassName Win32_LogicalDisk ... } [Token] 输入: 28 | 输出: 156 你: 再加上邮件告警功能 助手: 好的,在原有函数基础上增加邮件告警: function Get-DiskSpace { param( [string]$ComputerName = $env :COMPUTERNAME, [double]$ThresholdGB = 10 , ... [Token] 输入: 210 | 输出: 203 你: exit
可以看到第二轮对话中,输入 Token 从 28 涨到 210,因为整个对话历史都被带上了。这也提醒我们:多轮对话的 Token 消耗是递增的,长对话时需要考虑截断历史。
实战场景:代码审查助手 将 LLM 集成到日常工作流中,最实用的场景之一就是代码审查。我们读取一个脚本文件的内容,让模型从安全性、性能、可维护性等维度进行分析。
注意这里用数组拼接代替了 here-string,避免在内容中嵌入三反引号导致语法冲突。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 function Request-CodeReview { param ( [Parameter (Mandatory )] [string ]$ScriptPath ) $code = Get-Content $ScriptPath -Raw $promptParts = @ ( "请审查以下 PowerShell 脚本,指出潜在问题并提供改进建议:" "" "--- 脚本内容开始 ---" $code "--- 脚本内容结束 ---" "" "请从以下角度分析:" "1. 安全性(注入风险、凭据处理)" "2. 性能(循环优化、管道使用)" "3. 可维护性(命名规范、错误处理)" "4. 兼容性(PowerShell 版本差异)" ) $prompt = $promptParts -join "`n" $review = Invoke-LLMChat -Prompt $prompt -Model "gpt-4o" -MaxTokens 4096 Write-Host "`n========== 代码审查报告 ==========`n" -ForegroundColor Yellow Write-Host $review Write-Host "`n==================================`n" -ForegroundColor Yellow }
假设我们有一个脚本 cleanup.ps1,内容是简单的临时文件清理,执行审查后输出如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 PS> Request-CodeReview -ScriptPath .\cleanup.ps1 ========== 代码审查报告 ========== ## 代码审查结果 ### 1. 安全性 - 第 3 行使用了硬编码路径 `C:\Temp` ,建议改为参数化配置- 缺少 `-WhatIf` 支持,建议添加 `[SupportsShouldProcess()]` ### 2. 性能 - `Get-ChildItem` 未使用 `-File` 参数,可能误删目录- 建议添加 `-ErrorAction SilentlyContinue` 避免权限异常中断### 3. 可维护性 - 缺少注释和帮助文档(Comment-Based Help)- 变量 `$d` 命名不清晰,建议改为 `$daysOld` ### 4. 兼容性 - 使用了 PowerShell 7 的 `Ternernary` 运算符,Windows PowerShell 5.1 不兼容==================================
Token 用量估算 在频繁调用 API 的场景下,了解 Token 消耗非常重要。下面这个函数基于响应中的 usage 字段进行累计统计,帮你掌握每次调用的开销。
不同模型的单价不同,函数中提供了常见模型的参考价格(美元/千 Token),你可以根据实际情况调整。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 function Get-LLMTokenCost { param ( [int ]$PromptTokens , [int ]$CompletionTokens , [string ]$Model = "gpt-4o-mini" ) $pricing = @ { "gpt-4o" = @ { Input = 0.005 ; Output = 0.015 } "gpt-4o-mini" = @ { Input = 0.00015 ; Output = 0.0006 } "gpt-3.5-turbo" = @ { Input = 0.0005 ; Output = 0.0015 } } $rate = $pricing [$Model ] if (-not $rate ) { Write-Warning "未找到模型 $Model 的定价信息" return } $inputCost = [math ]::Round($PromptTokens * $rate .Input / 1000 , 6 ) $outputCost = [math ]::Round($CompletionTokens * $rate .Output / 1000 , 6 ) $totalCost = [math ]::Round($inputCost + $outputCost , 6 ) [PSCustomObject ]@ { 模型 = $Model 输入Token = $PromptTokens 输出Token = $CompletionTokens 总Token = $PromptTokens + $CompletionTokens 输入费用 = "`$$inputCost " 输出费用 = "`$$outputCost " 总费用 = "`$$totalCost " } }
使用示例:
1 2 3 4 5 > - - - - - - ---- -------- -------- ------- -------- -------- ------ - - . . .
可以看到,一次典型的多轮对话调用成本极低。但如果每天执行数百次自动化任务,费用仍然会累积,因此建议在脚本中加入 Token 上限控制。
注意事项
API Key 安全 :切勿将 Key 硬编码在脚本中或提交到代码仓库。使用环境变量或 Azure Key Vault 等密钥管理服务。可以在 .gitignore 中排除包含敏感信息的配置文件。
Token 限制 :每个模型有最大上下文窗口(如 gpt-4o-mini 为 128K)。多轮对话时注意累积的消息长度,必要时截断早期历史。建议在发送前估算 Token 数量(粗略规则:1 个汉字约 1-2 个 Token)。
国内代理 :如果无法直接访问 OpenAI API,可以设置 $env:OPENAI_API_BASE 指向国内代理。选择代理服务时注意数据隐私条款,避免敏感代码经第三方中转。
错误处理 :生产环境中建议在 Invoke-RestMethod 外包裹 try/catch,处理网络超时、API 限流(429 状态码)等异常情况。
流式响应 :本文示例使用非流式调用(等待完整响应后再返回)。如需实现打字机效果,可以使用 SSE(Server-Sent Events)模式,但实现复杂度较高,适合单独封装。