Skip to content

Instantly share code, notes, and snippets.

@SMSAgentSoftware
Created December 13, 2024 20:09
Show Gist options
  • Select an option

  • Save SMSAgentSoftware/361e12774c06e0675df85fa395d45d42 to your computer and use it in GitHub Desktop.

Select an option

Save SMSAgentSoftware/361e12774c06e0675df85fa395d45d42 to your computer and use it in GitHub Desktop.

Revisions

  1. SMSAgentSoftware created this gist Dec 13, 2024.
    308 changes: 308 additions & 0 deletions Invoke-OpenAIChat.ps1
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,308 @@
    function Invoke-OpenAIChat {
    <#
    .SYNOPSIS
    PowerShell function for OpenAI Chat API interaction via REST.
    .DESCRIPTION
    Provides a robust interface to OpenAI Chat API with features including:
    - Conversation history management
    - Token usage tracking
    - Markdown rendering support
    - Temperature control for response creativity
    - Verbosity levels for response detail
    - Browser-based markdown viewing option
    .PARAMETER Prompt
    The message to send to the OpenAI Chat API.
    Mandatory: Yes
    Pipeline: Accepts input
    Position: 0
    .PARAMETER Endpoint
    The OpenAI API endpoint URL.
    Mandatory: No
    Default: Standard endpoint URL
    Validation: Must be HTTPS URL
    .PARAMETER Model
    The deployment name of the AI model.
    Mandatory: No
    Default: o1-mini
    Valid values: You define the values that the parameter can accept.
    .PARAMETER KeyVaultName
    The name of your Azure Key Vault.
    Mandatory: No
    .PARAMETER SecretName
    The name of the secret in your Azure Key Vault containing the OpenAI API key.
    Mandatory: No
    .PARAMETER MaxTokens
    The maximum number of tokens to generate in the response.
    Mandatory: No
    Default: 16384
    Range: 1 - 16384
    .PARAMETER IncludeTokenUsage
    Switch to include token usage statistics in response.
    Mandatory: No
    .PARAMETER Temperature
    Controls response creativity and randomness.
    Mandatory: No
    Default: 0.2
    Range: 0.0 - 2.0
    .PARAMETER StartNewChat
    Switch to start a new chat session / clear chat history.
    Mandatory: No
    .PARAMETER ModelContext
    System context/prompt for AI behavior.
    Mandatory: No
    Default: PowerShell expert assistant context
    .PARAMETER NoMarkdown
    Switch to disable markdown rendering.
    Mandatory: No
    .PARAMETER UseBrowser
    Switch to view markdown in browser.
    Mandatory: No
    .PARAMETER Verbosity
    Controls response detail level.
    Mandatory: No
    Default: medium
    Valid values: none, low, medium, high
    .EXAMPLE
    PS> Invoke-OpenAIChat "Write a function to get system info"
    Returns AI-generated PowerShell code with explanations
    .EXAMPLE
    PS> "How to use arrays?" | Invoke-OpenAIChat -Temperature 0.8 -Verbosity high
    Returns detailed array explanation with high creativity
    .NOTES
    Author: Trevor Jones
    Version: 1.0
    Requires: Az.Accounts, Az.KeyVault modules
    #>
    [CmdletBinding()]
    param (
    # Main prompt parameter
    [Parameter(Mandatory = $true,
    ValueFromPipeline = $true,
    Position = 0)]
    [ValidateNotNullOrEmpty()]
    [string]$Prompt,

    # OpenAI endpoint URL
    [Parameter(Mandatory = $false)]
    [ValidatePattern('^https:\/\/.*')]
    [string]$Endpoint = "https://api.openai.com/v1/chat/completions",

    # AI model selection
    [Parameter(Mandatory = $false)]
    [ValidateSet("o1-preview","o1-mini","gpt-4o","chatgpt-4o-latest","gpt-4o-mini")]
    [string]$Model = "o1-mini",

    # Azure Key Vault name
    [Parameter(Mandatory = $false)]
    [ValidateNotNullOrEmpty()]
    [string]$KeyVaultName = "htsdesktopengkeyvault01",

    # Azure Key Vault secret name
    [Parameter(Mandatory = $false)]
    [ValidateNotNullOrEmpty()]
    [string]$SecretName = "OpenAI-API-Key",

    # Maximum number of tokens to generate
    [Parameter(Mandatory = $false)]
    [ValidateRange(1, 16384)]
    [int]$MaxTokens = 16384,

    # Token usage tracking switch
    [Parameter(Mandatory = $false)]
    [switch]$IncludeTokenUsage,

    # Response creativity control
    [Parameter(Mandatory = $false)]
    [ValidateRange(0.0, 2.0)]
    [double]$Temperature = 0.2,

    # New chat session control
    [Parameter(Mandatory = $false)]
    [switch]$StartNewChat,

    # AI behavior context
    [Parameter(Mandatory = $false)]
    [ValidateNotNullOrEmpty()]
    [string]$ModelContext = "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]$NoMarkdown,

    [Parameter(Mandatory = $false)]
    [switch]$UseBrowser,

    # Response detail level
    [Parameter(Mandatory = $false)]
    [ValidateSet("none", "low", "medium", "high")]
    [string]$Verbosity = "medium"
    )

    begin {
    # Module dependency check
    $requiredModules = @("Az.Accounts", "Az.KeyVault")

    # Find modules that are not installed
    $missingModules = $requiredModules | Where-Object { -not (Get-Module -Name $_ -ListAvailable) }

    # Install missing modules if any
    if ($missingModules) {
    try {
    Install-Module -Name $missingModules -Force -AllowClobber -Scope CurrentUser -Repository PSGallery -ErrorAction Stop
    }
    catch {
    throw "Failed to install module(s) '$($missingModules -join ', ')': $_"
    }
    }
    Import-Module Az.Accounts -ErrorAction Stop
    Import-Module Az.KeyVault -ErrorAction Stop

    # Retrieve secret from Azure Key Vault
    try
    {
    $secureSecretValue = Get-AzKeyVaultSecret -VaultName $KeyVaultName -Name $SecretName -ErrorAction Stop
    }
    catch
    {
    try
    {
    Connect-AzAccount -AuthScope KeyVault -WarningAction SilentlyContinue -ErrorAction Stop
    $secureSecretValue = Get-AzKeyVaultSecret -VaultName $KeyVaultName -Name $SecretName -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
    }

    # Set verbosity level text
    $verbosityText = switch ($Verbosity) {
    "none" { "Just provide the code and do not provide any explanation of it." }
    "low" { "Please keep the response concise and to the point. Focus on providing code, and just explain it very briefly." }
    "medium" { "Please keep the response concise. Provide a reasonable level of explanation for the code, adding some inline comments." }
    "high" { "Provide a detailed explanation of the code and add inline comments to it." }
    }
    $ModelContext += $verbosityText

    # Initialize chat history
    if ($StartNewChat -or -not (Get-Variable -Name openaichatmessages -ErrorAction SilentlyContinue)) {
    # o1 models require a temperature of 1 atm
    if ($Model -in ("o1-preview", "o1-mini")) {
    $role = "assistant"
    }
    else {
    $role = "system"
    }
    $global:openaichatmessages = @(@{
    role = $role
    content = $ModelContext
    })
    }
    }

    process {
    try {
    # Add user prompt to chat history
    $global:openaichatmessages += @{
    role = "user"
    content = $Prompt
    }

    # Create the headers for the request
    $headers = @{
    "Authorization" = "Bearer $secretValue"
    "content-type" = "application/json"
    }

    # o1 models require a temperature of 1 atm
    if ($Model -in ("o1-preview", "o1-mini")) {
    $Temperature = 1
    }

    # Create the body of the request
    $body = @{
    "model" = $Model
    "max_completion_tokens" = $MaxTokens
    "temperature" = $Temperature
    "messages" = @($global:openaichatmessages)
    } | ConvertTo-Json

    # Make API request with comprehensive error handling
    try {
    $response = Invoke-RestMethod -Uri $Endpoint -Method Post -Headers $headers -Body $body -ErrorAction 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()
    }
    }
    throw "API Request Failed ($statusCode): $errorMessage"
    }
    catch {
    throw "Unexpected error during API request: $_"
    }

    # Update chat history with response
    $global:openaichatmessages += @{
    role = 'assistant'
    content = $response.choices[0].message.content
    }

    # Format response
    $content = $response.choices[0].message.content
    if ($IncludeTokenUsage) {
    $content += "`n`nCompletion tokens: $($response.usage.completion_tokens) | "
    $content += "Prompt tokens: $($response.usage.prompt_tokens) | "
    $content += "Total tokens: $($response.usage.total_tokens)"
    }

    # Handle markdown rendering based on PowerShell version and preferences
    if ($PSVersionTable.PSVersion -ge [Version]"6.1" -and -not $NoMarkdown) {
    if ($UseBrowser) {
    return $content | Show-Markdown -UseBrowser
    }
    return $content | Show-Markdown
    }
    return $content
    }
    catch {
    Write-Error "Unexpected error: $_"
    return $null
    }
    }
    }