Skip to content

Instantly share code, notes, and snippets.

@SMSAgentSoftware
Last active March 18, 2025 14:56
Show Gist options
  • Save SMSAgentSoftware/5a7b83a6ddcf4575e8b6ef978e991bfa to your computer and use it in GitHub Desktop.
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.
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