Last active
March 18, 2025 14:56
-
-
Save SMSAgentSoftware/5a7b83a6ddcf4575e8b6ef978e991bfa to your computer and use it in GitHub Desktop.
PowerShell script to chat interactively with various AI models using their REST APIs. Requires API keys stored in an Azure KeyVault.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function Invoke-AIChat { | |
<# | |
.SYNOPSIS | |
Invokes an AI chat model to generate responses based on user prompts. | |
.DESCRIPTION | |
This function provides a unified interface to interact with various AI language model providers including: | |
- Azure OpenAI Services | |
- OpenAI API | |
- Anthropic Claude | |
- Google Gemini | |
- X AI (Grok) | |
- Perplexity AI | |
It supports features such as: | |
- Multiple model selection | |
- Context preservation | |
- Token usage tracking | |
- Cost estimation | |
- File input processing | |
- Markdown rendering | |
.PARAMETER Prompt | |
The main prompt to send to the AI model. This parameter is mandatory. | |
.PARAMETER Model | |
The AI model to use for generating responses. Defaults to "gpt-4o-mini". Valid options include: | |
- gpt-4o-mini | |
- gpt-4o | |
- claude-3-7-sonnet-latest | |
- claude-3-5-sonnet-latest | |
- claude-3-5-haiku-latest | |
- claude-3-opus-latest | |
- o1 | |
- o1-preview | |
- o1-mini | |
- o3-mini | |
- chatgpt-4o-latest | |
- gemini-2.0-flash | |
- gemini-2.0-flash-lite | |
- gemini-2.0-pro-exp | |
- gemini-1.5-flash | |
- gemini-1.5-pro | |
- grok-beta | |
- grok-2-latest | |
- sonar | |
- sonar-pro | |
- sonar-reasoning | |
- sonar-reasoning-pro | |
- sonar-deep-research | |
.PARAMETER InputFile | |
An optional input file or files to provide context for the AI model. The function supports various file formats including .txt, .md, .csv, .json, and more. | |
.PARAMETER MaxTokens | |
The maximum number of tokens to generate in the response. The default is 8192, and the value must be between 1 and 100000. Note that different models have different maximum token limits. | |
.PARAMETER Temperature | |
A value that controls the creativity of the response. Ranges from 0.0 to 2.0, with a default of 0.2. Note that different models have different temperature ranges. | |
.PARAMETER ReasoningEffort | |
A value of high, medium, or low that controls the reasoning effort of the response. Defaults to medium. Only applicable to the o1 and o3-mini models. | |
.PARAMETER IncludeTokenUsage | |
A switch that, when specified, includes token usage statistics in the output. | |
.PARAMETER IncludeCitations | |
A switch that, when specified, includes the internet references used to generate the reponse. Only applicable to the Perplexity AI models. | |
.PARAMETER NewChat | |
A switch that starts a new chat session, clearing the previous chat history. | |
.PARAMETER SystemPrompt | |
A string that sets the AI's behavior context. Defaults to a friendly assistant with expertise in PowerShell scripting. | |
.PARAMETER RawOutput | |
A switch that, when specified, returns raw output without markdown formatting. | |
.PARAMETER UseBrowser | |
A switch that, when specified, displays the output in a web browser using markdown rendering. | |
.EXAMPLE | |
# Simple query using default model | |
Invoke-AIChat -Prompt "What is PowerShell?" | |
.EXAMPLE | |
# Process a file with specific model and token tracking | |
Invoke-AIChat -Prompt "Analyze this code:" -InputFile ".\script.ps1" -Model "gpt-4o" -IncludeTokenUsage | |
.EXAMPLE | |
# Start new chat with custom system prompt | |
Invoke-AIChat -Prompt "Hello" -NewChat -SystemPrompt "You are a C# expert" -Model "claude-3.5-sonnet-latest" | |
.NOTES | |
Author: Trevor Jones | |
Date: 2025-03-18 | |
Version: 1.7 | |
Change log: | |
- 2025-03-18: v1.7 - Added support for gemini-2.0-flash, gemini-2.0-flash-lite and gemini-2.0-pro-exp models. Removed gemini-2.0-flash-exp | |
- 2025-03-14: v1.6 - Added support for sonar-deep-research model and basic support for claude-3-7-sonnet model (no reasoning yet) | |
- 2025-02-03: v1.5 - Added support for sonar-reasoning-pro and o3-mini models | |
- 2025-01-29: v1.4 - Added support for sonar-reasoning model | |
- 2025-01-22: v1.3 - Removed deprecated llama models from Perplexity AI and added sonar and sonar-pro models | |
- 2025-01-15: v1.2 - Added support for the o1 GA model | |
- 2024-12-25: v1.1 - Added support for Grok AI models | |
- 2024-12-18: Initial version | |
Requirements: | |
- Az.Accounts module | |
- Az.KeyVault module | |
- PowerShell 5.1 or later (7.0+ recommended) | |
- Azure subscription for Azure OpenAI | |
- API keys for other providers | |
#> | |
[CmdletBinding()] | |
param ( | |
# Main prompt parameter | |
[Parameter(Mandatory = $true, | |
ValueFromPipeline = $true, | |
Position = 0)] | |
[ValidateNotNullOrEmpty()] | |
[Alias("P")] | |
[string]$Prompt, | |
# AI model selection | |
[Parameter(Mandatory = $false)] | |
[ValidateSet("gpt-4o-mini","gpt-4o","claude-3-5-sonnet-latest","claude-3-7-sonnet-latest","claude-3-5-haiku-latest","claude-3-opus-latest","o1","o1-preview","o1-mini","o3-mini","chatgpt-4o-latest","gemini-2.0-flash","gemini-2.0-flash-lite","gemini-2.0-pro-exp","gemini-1.5-flash","gemini-1.5-pro","grok-beta","grok-2-latest","sonar","sonar-pro","sonar-reasoning","sonar-reasoning-pro","sonar-deep-research")] | |
[Alias("M")] | |
[string]$Model = "gpt-4o-mini", | |
# Optional input file for context | |
[Parameter(Mandatory=$false)] | |
[ValidateScript({ | |
$validExtensions = @(".txt", ".md", ".ps1", ".psm1", ".psd1", ".csv", ".log", ".xml", ".json", ".html", ".htm", ".css", ".js", ".sql", ".yaml", ".yml", ".ini", ".cfg", ".conf", ".config", ".bat", ".cmd", ".sh", ".bash", ".ps1xml", ".ps1xml", ".psm1xml",".docx",".rtf") | |
$extension = [System.IO.Path]::GetExtension($_).ToLower() | |
if ($validExtensions -contains $extension) { | |
$true | |
} else { | |
throw "Invalid file extension: $extension. Valid extensions are: $($validExtensions -join ', ')" | |
} | |
})] | |
[Alias("F")] | |
[string[]]$InputFile, | |
# Maximum number of tokens to generate | |
[Parameter(Mandatory = $false)] | |
[ValidateRange(1, 200000)] | |
[Alias("Tk")] | |
[int]$MaxTokens = 8192, | |
# Response creativity control | |
[Parameter(Mandatory = $false)] | |
[ValidateRange(0.0, 2.0)] | |
[Alias("T")] | |
[double]$Temperature = 0.2, | |
# Reasoning effort control for o1 model | |
[Parameter(Mandatory = $false)] | |
[ValidateSet("high", "medium", "low")] | |
[Alias("Re")] | |
[string]$ReasoningEffort = "medium", | |
# Token usage tracking switch | |
[Parameter(Mandatory = $false)] | |
[switch]$IncludeTokenUsage, | |
# Include web links used in the response. For Perplexity AI models only. | |
[Parameter(Mandatory = $false)] | |
[switch]$IncludeCitations, | |
# New chat session control | |
[Parameter(Mandatory = $false)] | |
[switch]$NewChat, | |
# AI behavior context | |
[Parameter(Mandatory = $false)] | |
[ValidateNotNullOrEmpty()] | |
[Alias("S")] | |
[string]$SystemPrompt = "You are a helpful and friendly assistant with expertise in PowerShell scripting and command line. Assume user is using the operating system 'Windows 11' unless otherwise specified. ", | |
# Output format controls | |
[Parameter(Mandatory = $false)] | |
[switch]$RawOutput, | |
[Parameter(Mandatory = $false)] | |
[switch]$UseBrowser | |
) | |
begin { | |
# Hash table to store model parameters. Update these values as necessary. | |
# Token costs are in USD per million tokens. This is divided by 1 million to get the cost per token. | |
# Context caching charges are not included in these costs. | |
# https://azure.microsoft.com/en-us/pricing/details/cognitive-services/openai-service/ | |
# https://docs.anthropic.com/en/docs/about-claude/models | |
# https://platform.openai.com/docs/models/gp | |
# https://ai.google.dev/gemini-api/docs/models/gemini | |
# https://docs.x.ai/docs/models | |
# https://docs.perplexity.ai/guides/pricing | |
$modelParamsTable = @{ | |
"gpt-4o-mini" = @{ | |
modelProvider = "Azure OpenAI" | |
inputCostPerToken = (0.15 / 1000000) -as [decimal] | |
outputCostPerToken = (0.6 / 1000000) -as [decimal] | |
maxTokens = 16384 | |
temperatureRange = 0.0, 2.0 | |
} | |
"gpt-4o" = @{ | |
modelProvider = "Azure OpenAI" | |
inputCostPerToken = (2.5 / 1000000) -as [decimal] | |
outputCostPerToken = (10 / 1000000) -as [decimal] | |
maxTokens = 16384 | |
temperatureRange = 0.0, 2.0 | |
} | |
"claude-3-7-sonnet-latest" = @{ | |
modelProvider = "Anthropic" | |
inputCostPerToken = (3 / 1000000) -as [decimal] | |
outputCostPerToken = (15 / 1000000) -as [decimal] | |
maxTokens = 8192 | |
temperatureRange = 0.0, 1.0 | |
} | |
"claude-3-5-sonnet-latest" = @{ | |
modelProvider = "Anthropic" | |
inputCostPerToken = (3 / 1000000) -as [decimal] | |
outputCostPerToken = (15 / 1000000) -as [decimal] | |
maxTokens = 8192 | |
temperatureRange = 0.0, 1.0 | |
} | |
"claude-3-5-haiku-latest" = @{ | |
modelProvider = "Anthropic" | |
inputCostPerToken = (0.8 / 1000000) -as [decimal] | |
outputCostPerToken = (4 / 1000000) -as [decimal] | |
maxTokens = 8192 | |
temperatureRange = 0.0, 1.0 | |
} | |
"claude-3-opus-latest" = @{ | |
modelProvider = "Anthropic" | |
inputCostPerToken = (15 / 1000000) -as [decimal] | |
outputCostPerToken = (75 / 1000000) -as [decimal] | |
maxTokens = 4096 | |
temperatureRange = 0.0, 1.0 | |
} | |
"o1" = @{ | |
modelProvider = "Azure OpenAI" | |
inputCostPerToken = (15 / 1000000) -as [decimal] | |
outputCostPerToken = (60 / 1000000) -as [decimal] | |
maxTokens = 200000 | |
temperatureRange = 0.0, 2.0 | |
} | |
"o1-preview" = @{ | |
modelProvider = "Azure OpenAI" | |
inputCostPerToken = (15 / 1000000) -as [decimal] | |
outputCostPerToken = (60 / 1000000) -as [decimal] | |
maxTokens = 128000 | |
temperatureRange = 0.0, 2.0 | |
} | |
"o1-mini" = @{ | |
modelProvider = "Azure OpenAI" | |
inputCostPerToken = (3 / 1000000) -as [decimal] | |
outputCostPerToken = (12 / 1000000) -as [decimal] | |
maxTokens = 128000 | |
temperatureRange = 0.0, 2.0 | |
} | |
"o3-mini" = @{ | |
modelProvider = "Azure OpenAI" | |
inputCostPerToken = (3 / 1000000) -as [decimal] | |
outputCostPerToken = (12 / 1000000) -as [decimal] | |
maxTokens = 200000 | |
temperatureRange = 0.0, 2.0 | |
} | |
"chatgpt-4o-latest" = @{ | |
modelProvider = "OpenAI" | |
inputCostPerToken = (2.5 / 1000000) -as [decimal] | |
outputCostPerToken = (10 / 1000000) -as [decimal] | |
maxTokens = 16384 | |
temperatureRange = 0.0, 2.0 | |
} | |
"gemini-2.0-flash" = @{ # Free until rate limits exceeded | |
modelProvider = "GoogleAI" | |
inputCostPerToken = (0.1 / 1000000) -as [decimal] | |
outputCostPerToken = (0.4 / 1000000) -as [decimal] | |
maxTokens = 8192 | |
temperatureRange = 0.0, 2.0 | |
} | |
"gemini-2.0-flash-lite" = @{ # Free until rate limits exceeded | |
modelProvider = "GoogleAI" | |
inputCostPerToken = (0.075 / 1000000) -as [decimal] | |
outputCostPerToken = (0.3 / 1000000) -as [decimal] | |
maxTokens = 8192 | |
temperatureRange = 0.0, 2.0 | |
} | |
"gemini-2.0-pro-exp" = @{ | |
modelProvider = "GoogleAI" | |
inputCostPerToken = (0 / 1000000) -as [decimal] | |
outputCostPerToken = (0 / 1000000) -as [decimal] | |
maxTokens = 8192 | |
temperatureRange = 0.0, 2.0 | |
} | |
"gemini-1.5-flash" = @{ # Free until rate limits exceeded | |
modelProvider = "GoogleAI" | |
inputCostPerToken = (0.075 / 1000000) -as [decimal] | |
outputCostPerToken = (0.3 / 1000000) -as [decimal] | |
maxTokens = 8192 | |
temperatureRange = 0.0, 2.0 | |
} | |
"gemini-1.5-pro" = @{ # Free until rate limits exceeded | |
modelProvider = "GoogleAI" | |
inputCostPerToken = (1.25 / 1000000) -as [decimal] | |
outputCostPerToken = (5 / 1000000) -as [decimal] | |
maxTokens = 8192 | |
temperatureRange = 0.0, 2.0 | |
} | |
"grok-beta" = @{ # Free credits until end 2024 | |
modelProvider = "XAI" | |
inputCostPerToken = (5 / 1000000) -as [decimal] | |
outputCostPerToken = (15 / 1000000) -as [decimal] | |
maxTokens = 131072 | |
temperatureRange = 0.0, 2.0 | |
} | |
"grok-2-latest" = @{ # Free credits until end 2024 | |
modelProvider = "XAI" | |
inputCostPerToken = (2 / 1000000) -as [decimal] | |
outputCostPerToken = (10 / 1000000) -as [decimal] | |
maxTokens = 131072 | |
temperatureRange = 0.0, 2.0 | |
} | |
"sonar" = @{ | |
modelProvider = "PerplexityAI" | |
inputCostPerToken = (1 / 1000000) -as [decimal] | |
outputCostPerToken = (1 / 1000000) -as [decimal] | |
costPerRequest = (5 / 1000) -as [decimal] | |
maxTokens = 127000 | |
temperatureRange = 0.0, 2.0 | |
} | |
# Pricing for sonar-pro and sonar-reasoning-pro is only approximate as the pricing model is different | |
# Cost per request is actually cost per number of search queries and is likely to be higher than 1 per request | |
# These models also charge for citation tokens which is not included here | |
"sonar-pro" = @{ | |
modelProvider = "PerplexityAI" | |
inputCostPerToken = (3 / 1000000) -as [decimal] | |
outputCostPerToken = (15 / 1000000) -as [decimal] | |
costPerRequest = (5 / 1000) -as [decimal] | |
maxTokens = 200000 | |
temperatureRange = 0.0, 2.0 | |
} | |
"sonar-reasoning" = @{ | |
modelProvider = "PerplexityAI" | |
inputCostPerToken = (1 / 1000000) -as [decimal] | |
outputCostPerToken = (5 / 1000000) -as [decimal] | |
costPerRequest = (5 / 1000) -as [decimal] | |
maxTokens = 127000 | |
temperatureRange = 0.0, 2.0 | |
} | |
"sonar-reasoning-pro" = @{ | |
modelProvider = "PerplexityAI" | |
inputCostPerToken = (2 / 1000000) -as [decimal] | |
outputCostPerToken = (8 / 1000000) -as [decimal] | |
costPerRequest = (5 / 1000) -as [decimal] | |
maxTokens = 127000 | |
temperatureRange = 0.0, 2.0 | |
} | |
"sonar-deep-research" = @{ | |
modelProvider = "PerplexityAI" | |
inputCostPerToken = (2 / 1000000) -as [decimal] | |
outputCostPerToken = (8 / 1000000) -as [decimal] | |
costPerRequest = (5 / 1000) -as [decimal] | |
maxTokens = 127000 | |
temperatureRange = 0.0, 2.0 | |
} | |
} | |
# A hash table of REST API parameters per AI model provider | |
# Azure OpenAI requires a unique endpoint for your AI project. | |
# OpenAI, Anthropic and Google have public endpoints. | |
$apiParamsTable = @{ | |
"Azure OpenAI" = @{ | |
endpoint = "https://ais-<my-azure-openai-endpoint>.openai.azure.com" | |
apiVersion = "2025-01-01-preview" | |
} | |
"OpenAI" = @{ | |
endpoint = "https://api.openai.com/v1/chat/completions" | |
azKeyVaultName = "<my-azure-keyvault-name>" | |
azKeyVaultSecretName = "OpenAI-API-Key" | |
} | |
"Anthropic" = @{ | |
endpoint = "https://api.anthropic.com/v1/messages" | |
apiVersion = "2023-06-01" | |
azKeyVaultName = "<my-azure-keyvault-name>" | |
azKeyVaultSecretName = "Anthropic-API-Key" | |
} | |
"GoogleAI" = @{ | |
endpoint = "https://generativelanguage.googleapis.com/v1beta/models" # or "https://generativelanguage.googleapis.com/v1/models" | |
azKeyVaultName = "<my-azure-keyvault-name>" | |
azKeyVaultSecretName = "Google-API-Key" | |
} | |
"XAI" = @{ | |
endpoint = "https://api.x.ai/v1/chat/completions" | |
azKeyVaultName = "<my-azure-keyvault-name>" | |
azKeyVaultSecretName = "XAI-API-Key" | |
} | |
"PerplexityAI" = @{ | |
endpoint = "https://api.perplexity.ai/chat/completions" | |
azKeyVaultName = "<my-azure-keyvault-name>" | |
azKeyVaultSecretName = "PerplexityAI-API-Key" | |
} | |
} | |
# Set the model and API parameters based on the model selection | |
$modelParams = $modelParamsTable[$Model] | |
$apiParams = $apiParamsTable[$modelParams.modelProvider] | |
# Validate max tokens based on model | |
if ($MaxTokens -gt $modelParams.maxTokens) { | |
throw "MaxTokens value exceeds the maximum allowed for the selected model: $($modelParams.maxTokens)" | |
} | |
# Validate temperature range based on model | |
if ($Temperature -lt $modelParams.temperatureRange[0] -or $Temperature -gt $modelParams.temperatureRange[1]) { | |
throw "Temperature value must be between $($modelParams.temperatureRange[0]) and $($modelParams.temperatureRange[1]) for the selected model" | |
} | |
# Module dependency checks | |
if (-not (Get-Module -Name Az.Accounts -ListAvailable)) { | |
try { | |
Install-Module -Name Az.Accounts -Force -AllowClobber -Scope CurrentUser -Repository PSGallery -ErrorAction Stop | |
} | |
catch { | |
throw "Failed to install Az.Accounts module: $_" | |
} | |
} | |
Import-Module Az.Accounts -ErrorAction Stop | |
# For anything other than Azure OpenAI, retrieve the API key from Azure Key Vault | |
If ($modelParams.modelProvider -ne "Azure OpenAI") { | |
if (-not (Get-Module -Name Az.KeyVault -ListAvailable)) { | |
try { | |
Install-Module -Name Az.KeyVault -Force -AllowClobber -Scope CurrentUser -Repository PSGallery -ErrorAction Stop | |
} | |
catch { | |
throw "Failed to install Az.KeyVault module: $_" | |
} | |
} | |
Import-Module Az.KeyVault -ErrorAction Stop | |
# Retrieve secret from Azure Key Vault | |
try | |
{ | |
$secureSecretValue = Get-AzKeyVaultSecret -VaultName $apiParams.azKeyVaultName -Name $apiParams.azKeyVaultSecretName -ErrorAction Stop -WarningAction SilentlyContinue | |
} | |
catch | |
{ | |
try | |
{ | |
# Silence breaking change warnings and survey prompts etc | |
$null = Update-AzConfig -DisplayBreakingChangeWarning $false -CheckForUpgrade $false -DisplayRegionIdentified $false -DisplaySurveyMessage $false -Scope Process | |
Connect-AzAccount -AuthScope KeyVault -InformationAction SilentlyContinue -WarningAction SilentlyContinue -ErrorAction Stop *>&1 | out-null | |
$secureSecretValue = Get-AzKeyVaultSecret -VaultName $apiParams.azKeyVaultName -Name $apiParams.azKeyVaultSecretName -ErrorAction Stop | |
} | |
catch | |
{ | |
Write-Error "An error occurred while retrieving the secret from Azure KeyVault: $_" | |
return $null | |
} | |
} | |
# Convert the secure string secret to plain text | |
if ($PSVersionTable.PSVersion -ge [Version]"7.0") { | |
$secretValue = $secureSecretValue.SecretValue | ConvertFrom-SecureString -AsPlainText | |
} | |
else { | |
$secretValue = [PSCredential]::new('dummy', $secureSecretValue.SecretValue).GetNetworkCredential().Password | |
} | |
} | |
# Initialize chat history | |
if ($NewChat -or -not (Get-Variable -Name aichathistory -Scope Global -ErrorAction SilentlyContinue)) { | |
$global:aichathistory = @{ | |
$model = @() | |
} | |
# Add system prompt to chat history for models that use it | |
switch ($modelParams.modelProvider) { | |
"Azure OpenAI" { | |
# o1 models use 'assistant' role | |
if ($Model -in ("o1-preview", "o1-mini")) { | |
$role = "assistant" | |
} | |
# o1 model use 'developer' role | |
elseif ($Model -in ("o1","o3-mini")) { | |
$role = "developer" | |
# if the RawOutput switch is NOT used, need to add 'Formatting re-enabled' to first line of system prompt to enable markdown formatting | |
if (-not $RawOutput) { | |
$SystemPrompt = "Formatting re-enabled`r`n" + $SystemPrompt | |
} | |
} | |
else { | |
$role = "system" | |
} | |
$global:aichathistory[$model] += @{ | |
role = $role | |
content = $SystemPrompt | |
} | |
} | |
"OpenAI" { | |
# o1 models use 'assistant' role | |
if ($Model -in ("o1-preview", "o1-mini")) { | |
$role = "assistant" | |
} | |
# o1 model use 'developer' role | |
elseif ($Model -in ("o1","o3-mini")) { | |
$role = "developer" | |
} | |
else { | |
$role = "system" | |
} | |
$global:aichathistory[$model] += @{ | |
role = $role | |
content = $SystemPrompt | |
} | |
} | |
"XAI" { | |
$global:aichathistory[$model] += @{ | |
role = 'system' | |
content = $SystemPrompt | |
} | |
} | |
"PerplexityAI" { # PerplexityAI models should use a different system prompt since they are online models returning web results. A system prompt is not essential for these models. | |
$global:aichathistory[$model] += @{ | |
role = 'system' | |
content = "You are an artificial intelligence assistant and you need to engage in a helpful, detailed, polite conversation with a user, providing the latest information on every request." | |
} | |
} | |
} | |
} | |
else { | |
if (-not $global:aichathistory.ContainsKey($model)) { | |
$global:aichathistory.$Model = @() | |
} | |
} | |
# Initialize chat session statistics | |
if (-not (Get-Variable -Name aichatsession -Scope Global -ErrorAction SilentlyContinue)) { | |
$global:aichatsession = @{ | |
$model = [ordered]@{ | |
inputTokensUsed = 0 | |
outputTokensUsed = 0 | |
totalTokensUsed = 0 | |
inputTokenCost = 0 | |
outputTokenCost = 0 | |
apiRequestCost = 0 | |
totalCost = 0 | |
} | |
totalSessionCost = 0 | |
} | |
} | |
else { | |
if (-not $global:aichatsession.ContainsKey($model)) { | |
$global:aichatsession.$Model = [ordered]@{ | |
inputTokensUsed = 0 | |
outputTokensUsed = 0 | |
totalTokensUsed = 0 | |
inputTokenCost = 0 | |
outputTokenCost = 0 | |
apiRequestCost = 0 | |
totalCost = 0 | |
} | |
} | |
} | |
} | |
process { | |
try { | |
If ($modelParams.modelProvider -eq "Azure OpenAI") { | |
# Get Azure access token with retry | |
try { | |
$secureAccessToken = Get-AzAccessToken -ResourceUrl "https://cognitiveservices.azure.com" -AsSecureString -WarningAction SilentlyContinue -ErrorAction Stop | | |
Select-Object -ExpandProperty Token | |
} | |
catch { | |
Write-Verbose "Initial token acquisition failed, attempting login..." | |
# Silence breaking change warnings and survey prompts etc | |
$null = Update-AzConfig -DisplayBreakingChangeWarning $false -CheckForUpgrade $false -DisplayRegionIdentified $false -DisplaySurveyMessage $false -Scope Process | |
Connect-AzAccount -InformationAction SilentlyContinue -WarningAction SilentlyContinue -ErrorAction Stop *>&1 | out-null | |
$secureAccessToken = Get-AzAccessToken -ResourceUrl "https://cognitiveservices.azure.com" -AsSecureString -WarningAction SilentlyContinue -ErrorAction Stop | | |
Select-Object -ExpandProperty Token | |
} | |
# Convert the secure string token to plain text | |
if ($PSVersionTable.PSVersion -ge [Version]"7.0") { | |
$accessToken = $secureAccessToken | ConvertFrom-SecureString -AsPlainText | |
} | |
else { | |
$accessToken = [PSCredential]::new('dummy', $secureAccessToken).GetNetworkCredential().Password | |
} | |
} | |
# Proces the input file/s if provided | |
if ($InputFile) { | |
$fileContent = [System.String]::Empty | |
foreach ($file in $InputFile) | |
{ | |
# Check if the file exists | |
if (-not (Test-Path -Path $file)) { | |
throw "File not found: $file" | |
} | |
# Ff the file is a DOCX file, use Word COM object to extract text | |
if ($extension -eq ".docx") { | |
try { | |
# Requires COM object (Windows only) | |
$word = New-Object -ComObject Word.Application | |
$doc = $word.Documents.Open($Path) | |
$content = $doc.Content.Text | |
$doc.Close() | |
$word.Quit() | |
return $content | |
} | |
catch { | |
Throw "Failed to process DOCX file: $_" | |
} | |
} | |
else { | |
$FileContent += (Get-Content -Path $file -Raw) + "`n`n" # Add line between files or any additional content | |
} | |
} | |
$Prompt = $FileContent + $Prompt | |
} | |
# Add user prompt to chat history | |
if ($modelParams.modelProvider -eq "GoogleAI") { | |
$global:aichathistory[$model] += @{ | |
role = 'user' | |
parts = @( | |
@{ | |
text = $Prompt | |
}) | |
} | |
} | |
else { | |
$global:aichathistory[$model] += @{ | |
role = 'user' | |
content = $Prompt | |
} | |
} | |
# Prepare headers per model provider | |
switch ($modelParams.modelProvider) { | |
"Azure OpenAI" { | |
$headers = @{ "Authorization" = "Bearer $accessToken" } | |
} | |
"OpenAI" { | |
$headers = @{ | |
"Authorization" = "Bearer $secretValue" | |
"content-type" = "application/json" | |
} | |
} | |
"Anthropic" { | |
$headers = @{ | |
"x-api-key" = $secretValue | |
"anthropic-version" = $apiParams.apiVersion | |
"content-type" = "application/json" | |
} | |
} | |
"GoogleAI" { | |
$headers = @{ | |
"content-type" = "application/json" | |
} | |
} | |
"XAI" { | |
$headers = @{ | |
"Authorization" = "Bearer $secretValue" | |
"content-type" = "application/json" | |
} | |
} | |
"PerplexityAI" { | |
$headers = @{ | |
"Authorization" = "Bearer $secretValue" | |
"content-type" = "application/json" | |
} | |
} | |
} | |
# Prepare request body per model provider | |
switch ($modelParams.modelProvider) { | |
"Azure OpenAI" { | |
# o1 models require a temperature of 1 atm. o1 and o3 include reasoning effort | |
if ($Model -in ("o1","o3-mini")) { | |
$Temperature = 1 | |
$body = @{ | |
temperature = $Temperature | |
max_completion_tokens = $MaxTokens | |
reasoning_effort = $ReasoningEffort | |
messages = @($global:aichathistory[$model]) | |
} | ConvertTo-Json | |
} | |
elseif ($Model -in ("o1-preview", "o1-mini")) { | |
$Temperature = 1 | |
$body = @{ | |
temperature = $Temperature | |
max_completion_tokens = $MaxTokens | |
messages = @($global:aichathistory[$model]) | |
} | ConvertTo-Json | |
} | |
else { | |
$body = @{ | |
temperature = $Temperature | |
max_completion_tokens = $MaxTokens | |
messages = @($global:aichathistory[$model]) | |
} | ConvertTo-Json | |
} | |
} | |
"OpenAI" { | |
# o1 models require a temperature of 1 atm | |
if ($Model -in ("o1","o3-mini")) { | |
$Temperature = 1 | |
$body = @{ | |
model = $Model | |
messages = @($global:aichathistory[$model]) | |
max_completion_tokens = $MaxTokens | |
reasoning_effort = $ReasoningEffort | |
temperature = $Temperature | |
} | ConvertTo-Json | |
} | |
elseif ($Model -in ("o1-preview", "o1-mini")) { | |
$Temperature = 1 | |
$body = @{ | |
model = $Model | |
messages = @($global:aichathistory[$model]) | |
max_completion_tokens = $MaxTokens | |
temperature = $Temperature | |
} | ConvertTo-Json | |
} | |
else { | |
$body = @{ | |
model = $Model | |
messages = @($global:aichathistory[$model]) | |
max_completion_tokens = $MaxTokens | |
temperature = $Temperature | |
} | ConvertTo-Json | |
} | |
} | |
"Anthropic" { | |
$body = @{ | |
model = $Model | |
messages = @($global:aichathistory[$model]) | |
max_tokens = $MaxTokens | |
system = $SystemPrompt | |
temperature = $Temperature | |
} | ConvertTo-Json | |
} | |
"GoogleAI" { | |
$body = @{ | |
contents = @($global:aichathistory[$model]) | |
generationConfig = @{ | |
temperature = $Temperature | |
maxOutputTokens = $MaxTokens | |
} | |
"systemInstruction" = @{ | |
role = "model" | |
parts = @( | |
@{ | |
"text" = $SystemPrompt | |
} | |
) | |
} | |
} | ConvertTo-Json -Depth 5 | |
} | |
"XAI" { | |
$body = @{ | |
model = $Model | |
max_tokens = $MaxTokens | |
temperature = $Temperature | |
messages = @($global:aichathistory[$model]) | |
} | ConvertTo-Json | |
} | |
"PerplexityAI" { | |
$body = @{ | |
model = $Model | |
messages = @($global:aichathistory[$model]) | |
max_tokens = $MaxTokens | |
temperature = $Temperature | |
} | ConvertTo-Json | |
} | |
} | |
# Make the API request per model provider | |
$stopWatch = [System.Diagnostics.Stopwatch]::StartNew() | |
try { | |
switch ($modelParams.modelProvider) { | |
"Azure OpenAI" { | |
$url = "$($apiParams.endpoint)/openai/deployments/$Model/chat/completions?api-version=$($apiParams.apiVersion)" | |
$response = Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body $body -ContentType 'application/json' -ErrorAction Stop | |
} | |
"OpenAI" { | |
$url = $apiParams.endpoint | |
$response = Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body $body -ErrorAction Stop | |
} | |
"Anthropic" { | |
$url = $apiParams.endpoint | |
$response = Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body $body -ErrorAction Stop | |
} | |
"GoogleAI" { | |
$url = "$($apiParams.endpoint)/$Model`:generateContent?key=$secretValue" | |
$response = Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body $body -ErrorAction Stop | |
} | |
"XAI" { | |
$url = $apiParams.endpoint | |
$response = Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body $body -ErrorAction Stop | |
} | |
"PerplexityAI" { | |
$url = $apiParams.endpoint | |
$response = Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body $body -ErrorAction Stop | |
} | |
} | |
$stopWatch.Stop() | |
} | |
catch [System.Net.WebException] { | |
$statusCode = [int]$_.Exception.Response.StatusCode | |
$errorMessage = switch ($statusCode) { | |
401 { "Unauthorized: Please check your authentication token" } | |
403 { "Forbidden: Access denied to the API endpoint" } | |
404 { "Not Found: Invalid endpoint or model deployment" } | |
429 { "Rate Limited: Too many requests, please try again later" } | |
500 { "Internal Server Error: Azure OpenAI service error" } | |
503 { "Service Unavailable: Azure OpenAI service is temporarily unavailable" } | |
default { | |
$reader = New-Object System.IO.StreamReader($_.Exception.Response.GetResponseStream()) | |
$reader.ReadToEnd() | |
} | |
} | |
$stopWatch.Stop() | |
throw "API Request Failed ($statusCode): $errorMessage" | |
} | |
catch { | |
$stopWatch.Stop() | |
throw "Unexpected error during API request: $_" | |
} | |
# Update chat history with response per model provider | |
switch ($modelParams.modelProvider) { | |
"Azure OpenAI" { | |
# o1 models use 'assistant' role, others use 'system' | |
if ($Model -in ("o1","o1-preview", "o1-mini","o3-mini")) { | |
$role = "assistant" | |
} | |
else { | |
$role = "system" | |
} | |
$global:aichathistory[$model] += @{ | |
role = $role | |
content = $response.choices[0].message.content | |
} | |
} | |
"OpenAI" { | |
# o1 models use 'assistant' role, others use 'system' | |
if ($Model -in ("o1","o1-preview", "o1-mini","o3-mini")) { | |
$role = "assistant" | |
} | |
else { | |
$role = "system" | |
} | |
$global:aichathistory[$model] += @{ | |
role = $role | |
content = $response.choices[0].message.content | |
} | |
} | |
"Anthropic" { | |
$global:aichathistory[$model] += @{ | |
role = 'assistant' | |
content = $response.content[0].text | |
} | |
} | |
"GoogleAI" { | |
$global:aichathistory[$model] += @{ | |
role = 'model' | |
parts = @( | |
@{ | |
text = $response.candidates[0].content.parts[0].text | |
} | |
) | |
} | |
} | |
"XAI" { | |
$global:aichathistory[$model] += @{ | |
role = 'assistant' | |
content = $response.choices[0].message.content | |
} | |
} | |
"PerplexityAI" { | |
$global:aichathistory[$model] += @{ | |
role = 'assistant' | |
content = $response.choices[0].message.content | |
} | |
} | |
} | |
# Format response per model provider | |
$content = switch ($modelParams.modelProvider) { | |
"Azure OpenAI" { $response.choices[0].message.content } | |
"OpenAI" { $response.choices[0].message.content } | |
"Anthropic" { $response.content[0].text } | |
"GoogleAI" { $response.candidates[0].content.parts[0].text } | |
"XAI" { $response.choices[0].message.content } | |
"PerplexityAI" { $response.choices[0].message.content } | |
} | |
if ($content.length -eq 0) { | |
$content = ">> No content received from the AI model. Check the maximum tokens setting - the output may have exceeded it. <<" | |
} | |
# Handle citations for PerplexityAI models | |
if ($IncludeCitations -and $modelParams.modelProvider -eq "PerplexityAI") { | |
$content += "`n`nReferences: `n$($response.citations -join " `n")" | |
} | |
# Calculate token usage and approximate cost by model provider | |
switch ($modelParams.modelProvider) { | |
"Azure OpenAI" { | |
$inputTokens = $response.usage.prompt_tokens | |
$outputTokens = $response.usage.completion_tokens | |
$totalTokens = $response.usage.total_tokens | |
$inputTokenCost = $inputTokens * $modelParams.inputCostPerToken | |
$outputTokenCost = $outputTokens * $modelParams.outputCostPerToken | |
$totalCost = $inputTokenCost + $outputTokenCost | |
} | |
"OpenAI" { | |
$inputTokens = $response.usage.prompt_tokens | |
$outputTokens = $response.usage.completion_tokens | |
$totalTokens = $response.usage.total_tokens | |
$inputTokenCost = $inputTokens * $modelParams.inputCostPerToken | |
$outputTokenCost = $outputTokens * $modelParams.outputCostPerToken | |
$totalCost = $inputTokenCost + $outputTokenCost | |
} | |
"Anthropic" { | |
$inputTokens = $response.usage.input_tokens | |
$outputTokens = $response.usage.output_tokens | |
$totalTokens = $inputTokens + $outputTokens | |
$inputTokenCost = $inputTokens * $modelParams.inputCostPerToken | |
$outputTokenCost = $outputTokens * $modelParams.outputCostPerToken | |
$totalCost = $inputTokenCost + $outputTokenCost | |
} | |
"GoogleAI" { | |
$inputTokens = $response.usageMetadata.promptTokenCount | |
$outputTokens = $response.usageMetadata.candidatesTokenCount | |
$totalTokens = $response.usageMetadata.totalTokenCount | |
$inputTokenCost = $inputTokens * $modelParams.inputCostPerToken | |
$outputTokenCost = $outputTokens * $modelParams.outputCostPerToken | |
$totalCost = $inputTokenCost + $outputTokenCost | |
} | |
"XAI" { | |
$inputTokens = $response.usage.prompt_tokens | |
$outputTokens = $response.usage.completion_tokens | |
$totalTokens = $response.usage.total_tokens | |
$inputTokenCost = $inputTokens * $modelParams.inputCostPerToken | |
$outputTokenCost = $outputTokens * $modelParams.outputCostPerToken | |
$totalCost = $inputTokenCost + $outputTokenCost | |
} | |
"PerplexityAI" { | |
$inputTokens = $response.usage.prompt_tokens | |
$outputTokens = $response.usage.completion_tokens | |
$totalTokens = $response.usage.total_tokens | |
$inputTokenCost = $inputTokens * $modelParams.inputCostPerToken | |
$outputTokenCost = $outputTokens * $modelParams.outputCostPerToken | |
$apiRequestCost = $modelParams.costPerRequest | |
$totalCost = $inputTokenCost + $outputTokenCost + $apiRequestCost | |
} | |
} | |
# Update chat session statistics | |
$global:aichatsession[$model].inputTokensUsed += $inputTokens | |
$global:aichatsession[$model].outputTokensUsed += $outputTokens | |
$global:aichatsession[$model].totalTokensUsed += $totalTokens | |
$global:aichatsession[$model].inputTokenCost += $inputTokenCost | |
$global:aichatsession[$model].outputTokenCost += $outputTokenCost | |
if ($apiRequestCost) { | |
$global:aichatsession[$model].apiRequestCost += $apiRequestCost | |
} | |
$global:aichatsession[$model].totalCost += $totalCost | |
$global:aichatsession.totalSessionCost += $totalCost | |
# Prepare response content with token usage statistics if requested | |
if ($IncludeTokenUsage) { | |
$content += "`n`nInput tokens: $inputTokens | " | |
$content += "Output tokens: $outputTokens | " | |
$content += "Total tokens: $totalTokens" | |
$content += " `nInput token cost (`$US): $inputTokenCost | " | |
$content += "Output token cost (`$US): $outputTokenCost | " | |
if ($apiRequestCost) { | |
$content += "API request cost (`$US): $apiRequestCost | " | |
} | |
$content += "Total cost (`$US): $totalCost" | |
$content += " `nResponse time (sec): $([math]::Round($stopWatch.Elapsed.TotalSeconds,2))" | |
} | |
# Handle markdown rendering based on PowerShell version and preferences | |
if ($PSVersionTable.PSVersion -ge [Version]"6.1" -and -not $RawOutput) { | |
if ($UseBrowser) { | |
return $content | Show-Markdown -UseBrowser | |
} | |
return $content | Show-Markdown | |
} | |
return $content | |
} | |
catch { | |
Write-Error "Unexpected error: $_" | |
return $null | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment