Public/Invoke-AIFunctionBuilder.ps1
function Invoke-AIFunctionBuilder { <# .SYNOPSIS Create a PowerShell function with the help of ChatGPT .DESCRIPTION Invoke-AIFunctionBuilder is a function that uses ChatGPT to generate an initial PowerShell function to achieve the goal defined in the prompt by the user but goes a few steps beyond the typical interaction with an LLM by auto-validating the result of the AI generated script using parsing techniques that feed common issues back to the model until it resolves them. .EXAMPLE PS>Invoke-AIFunctionBuilder # The function builder renders the UI and asks the user to enter a prompt to generate a function .EXAMPLE PS>Invoke-AIFunctionBuilder -Prompt "Write a powershell function that will show a date and time in timestamp form" -NonInteractive function Get-Timestamp { return (Get-Date).ToString("yyyy-MM-ddTHH:mm:ss.fffffZ") } .EXAMPLE PS>$function = 'function Write-Hello { Write-Output "hello world" }' PS>Invoke-AIFunctionBuilder -InitialFunction $function -Prompt "write a powershell function that says hello" # The function builder renders the UI and validates the function provided meets the goal of the prompt .NOTES Author: Shaun Lawrie / @shaun_lawrie #> [CmdletBinding(DefaultParameterSetName = 'Interactive')] [alias("ifb")] param( # A prompt in the format "Write a powershell function that will sing me happy birthday" [Parameter(ParameterSetName="Interactive", ValueFromPipeline = $true)] [Parameter(ParameterSetName="NonInteractive", ValueFromPipeline = $true, Mandatory=$true)] [string] $Prompt, # The maximum loop iterations to attempt to generate the function within [Parameter(ParameterSetName="Interactive")] [Parameter(ParameterSetName="NonInteractive")] [int] $MaximumReinforcementIterations = 15, # Return the code result without showing any interactions [Parameter(ParameterSetName="NonInteractive")] [switch] $NonInteractive, # The model to use [Parameter(ParameterSetName="Interactive")] [Parameter(ParameterSetName="NonInteractive")] [ValidateSet("gpt-3.5-turbo", "gpt-4")] [string] $Model = "gpt-3.5-turbo", # A seed function to use as the function builder starting point, this can allow you to iterate on an existing idea [Parameter(ParameterSetName="Interactive")] [Parameter(ParameterSetName="NonInteractive")] [string] $InitialFunction ) $fullPrompt = $Prompt if(-not $NonInteractive) { Clear-Host $prePrompt = $null if([string]::IsNullOrEmpty($Prompt)) { $version = if($PSVersionTable.PSVersion.Major -gt 5) { "core" } else { $PSVersionTable.PSVersion.Major } $prePrompt = "Write a PowerShell $version function that will" Write-Host -ForegroundColor Cyan -NoNewline "${prePrompt}: " $Prompt = Read-Host if([string]::IsNullOrWhiteSpace($Prompt)) { Write-Host "No prompt was provided, I guess you're feeling lucky..." $Prompt = "do something" } } $fullPrompt = (@($prePrompt, $Prompt) | Where-Object { $null -ne $_ }) -join ' ' } try { $function = Initialize-AifbFunction -Prompt $fullPrompt -Model $Model -InitialFunction $InitialFunction Initialize-AifbRenderer -InitialPrePrompt $prePrompt -InitialPrompt $Prompt -NonInteractive $NonInteractive Write-AifbFunctionOutput -FunctionText $function.Body -Prompt $fullPrompt $function = Optimize-AifbFunction -Function $function -Prompt $fullPrompt -Force:(![string]::IsNullOrWhiteSpace($InitialFunction)) if($NonInteractive) { return $function.Body } Write-AifbFunctionOutput -FunctionText $function.Body -SyntaxHighlight -NoLogMessages -Prompt $fullPrompt $finished = $false while(-not $finished) { $action = Get-AifbUserAction -Function $function switch($action) { "Edit" { $editPrePrompt = "`nI also want the function to" Write-Host -ForegroundColor Cyan -NoNewline "${editPrePrompt}: " $editPrompt = Read-Host Write-Verbose "Re-running function optimizer with a request to edit functionality: '$editPrompt'" $fullPrompt = (@($fullPrompt, $editPrompt) | Where-Object { ![string]::IsNullOrWhiteSpace($_) }) -join ' and the function must ' Write-AifbFunctionOutput -FunctionText $function.Body -Prompt $fullPrompt $function = Optimize-AifbFunction -Function $function -Prompt $fullPrompt -RuntimeError "The function does not meet all conditions in the prompt ($fullPrompt)." Write-AifbFunctionOutput -FunctionText $function.Body -SyntaxHighlight -NoLogMessages -Prompt $fullPrompt } "Copy" { Set-Clipboard -Value $function.Body Write-Host "The function code has been copied to your clipboard!" if($IsLinux) { Write-Warning "This might not work under WSL, you can try the 'Save' option to save the function to your local filesystem instead." } Write-Host "" } "Explain" { $explanation = (Get-GPT3Completion -prompt "Explain how the function below meets all of the requirements the following requirements, list the requirements and how each is met in a numbered list. Also provide a summary of what the function can do.`nRequirements: $fullPrompt`n`n``````powershell`n$($function.Body)``````" -max_tokens 2000).Trim() Write-AifbFunctionOutput -FunctionText $function.Body -SyntaxHighlight -NoLogMessages -Prompt $fullPrompt Write-Host $explanation Write-Host "" } "Run" { $tempFile = New-TemporaryFile $tempFilePsm1 = "$($tempFile.FullName).psm1" Set-Content -Path $tempFile -Value $function.Body Move-Item -Path $tempFile.FullName -Destination $tempFilePsm1 Write-Host "" Import-Module $tempFilePsm1 -Global $commands = (Get-Module | Where-Object { $_.Path -eq $tempFilePsm1 }).ExportedCommands.Keys $command = Get-Command $commands[0] if($commands.Count -gt 1) { while($null -eq $command) { $commandName = (Read-Host "There are multiple functions in this module ($($commands -join ', ')), enter the name of the one you want to use as the entry point").Trim() $command = Get-Command $commandName -ErrorAction "SilentlyContinue" if(!$command) { Write-Warning "Command name '$commandName' failed to import a command." } } } $params = @{} if($command.ParameterSets) { $command.ParameterSets.GetEnumerator()[0].Parameters | Where-Object { $_.Position -ge 0 } | Foreach-Object { $params[$_.Name] = Read-Host "$($_.Name) ($($_.ParameterType))" } } $previousErrorActionPreference = $ErrorActionPreference try { & $function.Name @params -ErrorAction "Stop" | Out-Host Get-Module | Where-Object { $_.Path -eq $tempFilePsm1 } | Remove-Module -Force $answer = Read-Host -Prompt "Are there any issues that need correcting? (y/n)" if($answer -eq "y") { $issueDescription = Read-Host -Prompt "Describe the issues" Write-AifbFunctionOutput -FunctionText $function.Body -Prompt $fullPrompt $function = Optimize-AifbFunction -Function $function -Prompt $fullPrompt -RuntimeError $issueDescription Write-AifbFunctionOutput -FunctionText $function.Body -SyntaxHighlight -NoLogMessages -Prompt $fullPrompt } } catch { Get-Module | Where-Object { $_.Path -eq $tempFilePsm1 } | Remove-Module -Force Write-Error $_ $answer = Read-Host -Prompt "An error occurred, do you want to try auto-fix the function? (y/n)" if($answer -eq "y") { Write-AifbFunctionOutput -FunctionText $function.Body -Prompt $fullPrompt $function = Optimize-AifbFunction -Function $function -Prompt $fullPrompt -RuntimeError $_.Exception.Message Write-AifbFunctionOutput -FunctionText $function.Body -SyntaxHighlight -NoLogMessages -Prompt $fullPrompt } } Write-Host "" $ErrorActionPreference = $previousErrorActionPreference } "Save" { $moduleLocation = Save-AifbFunctionOutput -FunctionText $function.Body -FunctionName $function.Name -Prompt $fullPrompt Import-Module $moduleLocation -Global Write-Host "The function is available as '$($function.Name)' in your current terminal session. To import this function in the future use 'Import-Module $moduleLocation' or add the directory with all your PowerShellAI modules to your `$env:PSModulePath to have them auto import for every session." $finished = $true } "Quit" { $finished = $true } } } } finally { Stop-Chat } } |