Public/Get-ChatCompletion.ps1
<#
NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE This script is a work in progress. It is a ** Proof of Concept ** It has proven to be a very useful tool. I am releasing it as a preview to get feedback. Next steps are to refactor the code, add unit tests, add help, vidoes and add more features. Easiest way to get started: 1. Install-Module PowerShellAI ```powershell new-chat 'you are a powershell bot' chat 'even numbers btwn 1 and 10' chat 'odd numbers' ``` These functions enable having a chat session with a GPT model. It has "memory" and keeps track of the conversation. More to come.... #> $Script:messages $Script:timeStamp = $null if ($IsWindows) { $Script:chatSessionPath = Join-Path $env:APPDATA 'PowerShellAI/ChatGPT' } else { $Script:chatSessionPath = Join-Path $env:HOME '~/PowerShellAI/ChatGPT' } $Script:chatInProgress = $false function Test-ChatInProgress { $Script:chatInProgress } function Get-ChatTheme { if (Test-ChatInProgress) { $Script:messages[0].content } else { "No chat in progress. Use `Chat <content>` or `New-Chat <theme>` to start a new chat session." } } function Stop-Chat { $Script:chatInProgress = $false $Script:messages = @() $Script:timeStamp = $null } function Get-ChatHistory { if (Test-ChatInProgress) { if ($Script:messages.Count -gt 0) { foreach ($message in $Script:messages) { [PSCustomObject]$message } } else { "No chat history" } } else { "No chat in progress. Use `Chat <content>` or `New-Chat <theme>` to start a new chat session." } } function Get-ChatInProgress { if (Test-ChatInProgress) { Get-ChatHistory } else { "No chat in progress. Use `New-Chat` to start a new chat session." } } function Set-TimeStamp { param( $targetTimeStamp ) if ($null -ne $targetTimeStamp) { $Script:timeStamp = $targetTimeStamp } else { $Script:timeStamp = Get-Date -Format 'yyyyMMddHHmmss' } } function New-Chat { param( $prompt ) if ($null -ne $Script:timeStamp) { Export-ChatSession } $Script:chatInProgress = $true $Script:messages = @() Set-TimeStamp if ($null -ne $prompt) { New-ChatMessage -Role system -Content $prompt } } function Get-ChatSessionPath { $Script:chatSessionPath } function Export-ChatSession { $file = Join-Path $Script:chatSessionPath ("{0}-ChatGPTSession.xml" -f $Script:timeStamp) if (-not (Test-Path $Script:chatSessionPath)) { $null = New-Item -ItemType Directory -Path $Script:chatSessionPath } $Script:messages | Export-Clixml -Path $file -Force } function Import-ChatSession { param( [Parameter(ValueFromPipelineByPropertyName)] [Alias('FullName')] $Path ) End { if ($null -eq $Path -or -not (Test-Path $Path) ) { throw "Chat session file not found or is empty: $Path" } $Script:messages = Import-Clixml -Path $Path Set-TimeStamp ((Split-Path -Leaf $Path) -split '-')[0] $Script:chatInProgress = $true } } function Get-ChatSession { param( $Name ) if (Test-Path $Script:chatSessionPath) { Get-ChildItem -Path $Script:chatSessionPath *.xml | Where-Object { $_.Name -match $Name } } } function Get-ChatSessionContent { param( [Alias('FullName')] [Parameter(ValueFromPipelineByPropertyName)] $Path ) Process { (Import-Clixml -Path $Path) | ForEach-Object { $SessionName = (Split-Path -Leaf $Path) -replace '-ChatGPTSession.xml', '' [PSCustomObject]$_ | Add-Member -PassThru -MemberType NoteProperty -Name SessionName -Value $SessionName } | Select-Object SessionName, Role, Content } } function Invoke-ChatCompletion { [CmdletBinding()] [alias("chat")] param( [Parameter(Mandatory)] $prompt, [switch]$FastDisplay ) if (!(Test-ChatInProgress)) { New-Chat } Write-ChatResponse -Role user -Content $prompt } function Import-ChatMessages { param( [Parameter(ValueFromPipelineByPropertyName)] $Content, [Parameter(ValueFromPipelineByPropertyName)] [ValidateSet('user', 'system', 'assistant')] $Role ) Begin { if (!(Test-ChatInProgress)) { New-Chat } } Process { if ($Content -is [string]) { $targetContent = $Content } else { $targetContent = $Content.Content } #New-ChatMessage -Role $Role -Content $Content #.Content New-ChatMessage -Role $Role -Content $targetContent } } function Import-ChatAssistantMessages { param( [Parameter(ValueFromPipelineByPropertyName, ValueFromPipeline)] $Content ) Process { [PSCustomObject]@{ role = 'assistant' content = $Content } | Import-ChatMessages } } function Import-ChatUserMessages { param( [Parameter(ValueFromPipelineByPropertyName, ValueFromPipeline)] $Content ) Process { [PSCustomObject]@{ role = 'user' content = $Content.Content } | Import-ChatMessages } } function New-ChatMessage { param( [Parameter(Mandatory)] [ValidateSet('user', 'system', 'assistant')] $Role, [Parameter(Mandatory)] $Content ) $Script:messages += @( @{ role = $Role content = $Content } ) Export-ChatSession } function Get-OpenAIChatPayload { param( $model = 'gpt-3.5-turbo', $temperature = 0.0, $max_tokens = 256, $top_p = 1.0, $frequency_penalty = 0, $presence_penalty = 0, $stop ) $payLoad = [ordered]@{ model = $model messages = $Script:messages temperature = $temperature max_tokens = $max_tokens top_p = $top_p frequency_penalty = $frequency_penalty presence_penalty = $presence_penalty stop = $stop } $payLoad | ConvertTo-Json -Depth 5 } function Write-ChatResponse { param( [Parameter(Mandatory)] [ValidateSet('user', 'system')] $Role, [Parameter(Mandatory)] $Content ) New-ChatMessage -Role $Role -Content $prompt $body = Get-OpenAIChatPayload $result = Invoke-OpenAIAPI -Uri (Get-OpenAIChatCompletionUri) -Method 'Post' -Body $body if (!$ExcludeResponseFromBeingSaved) { New-ChatMessage -Role assistant -Content $result.choices[0].message.content } if ($Raw) { $result } elseif ($result.choices) { $content = $result.choices[0].message.content if ($FastDisplay) { Write-Host $content } else { $content.ToCharArray() | ForEach-Object { Write-Host -NoNewline $_; Start-Sleep -Milliseconds 1 } } } '' } function Get-ChatCompletion { <# .SYNOPSIS Get a completion from the OpenAI GPT-3 API .DESCRIPTION Given a prompt, the model will return one or more predicted completions, and can also return the probabilities of alternative tokens at each position .PARAMETER prompt The prompt to generate completions for .PARAMETER model ID of the model to use. Defaults to 'text-davinci-003' .PARAMETER temperature The temperature used to control the model's likelihood to take risky actions. Higher values means the model will take more risks. Try 0.9 for more creative applications, and 0 (argmax sampling) for ones with a well-defined answer. Defaults to 0 .PARAMETER max_tokens The maximum number of tokens to generate. By default, this will be 64 if the prompt is not provided, and 1 if a prompt is provided. The maximum is 2048 .PARAMETER top_p An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. Defaults to 1 .PARAMETER frequency_penalty A value between 0 and 1 that penalizes new tokens based on whether they appear in the text so far. Defaults to 0 .PARAMETER presence_penalty A value between 0 and 1 that penalizes new tokens based on whether they appear in the text so far. Defaults to 0 .PARAMETER stop A list of tokens that will cause the API to stop generating further tokens. By default, the API will stop generating when it hits one of the following tokens: ., !, or ?. .EXAMPLE Get-GPT3Completion -prompt "What is 2%2? - please explain" #> [CmdletBinding()] [alias("chatgpt")] param( # [Parameter(Mandatory)] $prompt, $model = 'gpt-3.5-turbo', [ValidateRange(0, 2)] [decimal]$temperature = 0.0, [ValidateRange(1, 4096)] [int]$max_tokens = 256, [ValidateRange(0, 1)] [decimal]$top_p = 1.0, [ValidateRange(-2, 2)] [decimal]$frequency_penalty = 0, [ValidateRange(-2, 2)] [decimal]$presence_penalty = 0, $stop, [Switch]$Raw, [Switch]$FastDisplay, [Switch]$ExcludeResponseFromBeingSaved ) New-Chat if ($prompt) { New-ChatMessage -Role 'system' -Content $prompt } function AnotherQuestion { $prompt = Read-Host -Prompt 'Please tell me what you would like to know' Write-ChatResponse -Role 'user' -Content $prompt } function NewChat { New-Chat $prompt = Read-Host -Prompt 'What is the theme of your chat' New-ChatMessage -Role 'system' -Content $prompt AnotherQuestion } function RunCode { Write-Host "Run: Not yet implemented`r`n" -ForegroundColor Red } function SaveCode { Write-Host "Save: Not yet implemented`r`n" -ForegroundColor Red } function StopChat { break } function ClearScreen { Clear-Host } [System.Collections.ArrayList]$map = @() function New-MenuOption { param( [System.Management.Automation.Host.ChoiceDescription]$ChoiceDescription, $Action ) $null = $map.Add(@{ ChoiceDescription = $ChoiceDescription Action = $Action }) } New-MenuOption (New-Object System.Management.Automation.Host.ChoiceDescription '&Another question', 'Do a follow up question') AnotherQuestion New-MenuOption (New-Object System.Management.Automation.Host.ChoiceDescription '&New Chat', 'Start a new chat') NewChat New-MenuOption (New-Object System.Management.Automation.Host.ChoiceDescription '&Run', 'Run the code') RunCode New-MenuOption (New-Object System.Management.Automation.Host.ChoiceDescription '&Save', 'Save the code') SaveCode New-MenuOption (New-Object System.Management.Automation.Host.ChoiceDescription '&Clear Screen', 'Clear the screen') ClearScreen New-MenuOption (New-Object System.Management.Automation.Host.ChoiceDescription '&Quit', 'Stop the chat') StopChat $descriptions = foreach ($item in $map) { $item.ChoiceDescription } $options = [System.Management.Automation.Host.ChoiceDescription[]]($descriptions) AnotherQuestion while ($true) { $message = "`r`nWhat would you like to do next?" $response = $host.ui.PromptForChoice($null, $message, $options, 0) &$map[$response].Action } } |