Private/Invoke-PSAOAIApiRequestStream.ps1
Add-Type -AssemblyName System.Net.Http <# Problem with streaming response, similar: https://github.com/PowerShell/PowerShell/issues/23783 can be connected: https://stackoverflow.com/questions/60707843/806889 solved: modify the code to use SendAsync with HttpCompletionOption.ResponseHeadersRead. This will return control to your code as soon as the headers are read, allowing you to start processing the response body as a stream immediately. This modification will send the HTTP POST request with HttpCompletionOption.ResponseHeadersRead, which means that the SendAsync method will complete as soon as it has read the headers from the response. This allows you to start reading the response body as a stream immediately, which can give you a streaming-like behavior. #> # This function makes an API request and stores the response function Invoke-PSAOAIApiRequestStream { <# .SYNOPSIS Invokes the Azure OpenAI API using a stream request. .DESCRIPTION This function sends a POST request to the Azure OpenAI API and processes the response as a stream. This is particularly useful for large responses that can be processed incrementally. .PARAMETER Url The URL of the Azure OpenAI API endpoint. .PARAMETER Headers A hashtable containing any custom headers to include in the API request, including the required API key. .PARAMETER BodyJSON The JSON-formatted request body to send to the API. .PARAMETER Chat A switch parameter to indicate if the request is for a chat completion. .PARAMETER Logfile The path to the log file where the function will write log messages. .PARAMETER Timeout The timeout in seconds for the API request. Default is 60 seconds. .EXAMPLE $apiUrl = "https://api.openai.com/v1/endpoint" $requestBody = @{ prompt = "Translate this English text to French." } | ConvertTo-Json $headers = @{ "api-key" = "your_api_key_here" } $response = Invoke-PSAOAIApiRequestStream -Url $apiUrl -Headers $headers -BodyJSON $requestBody # Process the response stream (e.g., read and parse the data) .NOTES Author: voytas Date: 2024-05-28 #> param( [Parameter(Mandatory = $true)] [string]$url, # The URL for the API request [Parameter(Mandatory = $true)] [hashtable]$headers, # The headers for the API request [Parameter(Mandatory = $true)] [string]$bodyJSON, # The body for the API request [Parameter(Mandatory = $false)] [switch]$Chat, [Parameter(Mandatory = $false)] [string]$logfile, [Parameter(Mandatory = $false)] $timeout = 60 # The timeout for the API request ) # Try to send the API request and handle any errors try { # Create an instance of HttpClientHandler and disable buffering $httpClientHandler = [System.Net.Http.HttpClientHandler]::new() $httpClientHandler.AllowAutoRedirect = $false $httpClientHandler.UseCookies = $false $httpClientHandler.AutomaticDecompression = [System.Net.DecompressionMethods]::GZip -bor [System.Net.DecompressionMethods]::Deflate # Create an instance of HttpClient $httpClient = [System.Net.Http.HttpClient]::new($httpClientHandler) #$httpClient = [System.Net.Http.HttpClient]::new() Write-LogMessage "HttpClient instance created with custom handler." -LogFile $logfile # Set the required headers $httpClient.DefaultRequestHeaders.Add("api-key", $($headers."api-key")) Write-LogMessage "API key header added." -LogFile $logfile # Set the timeout for the HttpClient $httpClient.Timeout = New-TimeSpan -Seconds $timeout # Create the HttpContent object with the request body $content = [System.Net.Http.StringContent]::new($bodyJSON, [System.Text.Encoding]::UTF8, "application/json") Write-LogMessage "HttpContent created with request body." -LogFile $logfile $request = New-Object System.Net.Http.HttpRequestMessage ([System.Net.Http.HttpMethod]::Post, $url) $request.Content = $content # Send the HTTP POST request asynchronously with HttpCompletionOption.ResponseHeadersRead $response = $httpClient.SendAsync($request, [System.Net.Http.HttpCompletionOption]::ResponseHeadersRead).Result Write-LogMessage "HTTP POST request sent to ${url}." -LogFile $logfile #Write-LogMessage "HTTP POST request sent to ${url}." -LogFile $logfile # Ensure the request was successful if ($response.IsSuccessStatusCode) { Write-LogMessage "Received successful response from server." -LogFile $logfile # Inspect headers for Transfer-Encoding $transferEncoding = $response.Headers.TransferEncoding.ToString() #$transferEncoding | ConvertTo-Json | Write-LogMessage -LogFile $logfile if ($transferEncoding -contains "chunked") { Write-LogMessage "The API endpoint supports streaming (chunked transfer encoding)." -LogFile $logfile } else { Write-LogMessage "The API endpoint does not support streaming (no chunked transfer encoding)." -LogFile $logfile } } else { Write-LogMessage "Received error response: $($response.StatusCode) - $($response.ReasonPhrase)" "ERROR" -LogFile $logfile throw "Error in HTTP response: $($response.StatusCode) - $($response.ReasonPhrase)" } # Get the response stream $stream = $response.Content.ReadAsStreamAsync().Result $reader = [System.IO.StreamReader]::new($stream) Write-LogMessage "Response stream obtained and StreamReader initialized." -LogFile $logfile # Initialize the completeText variable $completeText = "" # Read and output each line from the response stream #while (-not $reader.EndOfStream) { # Write-Information ($reader.ReadLine()) -InformationAction Continue #} while ($null -ne ($line = $reader.ReadLine()) -or (-not $reader.EndOfStream)) { # Log each received line #Write-LogMessage "Received line: $line" -LogFile $logfile # Check if the line starts with "data: " and is not "data: [DONE]" if ($line.StartsWith("data: ") -and $line -ne "data: [DONE]") { # Extract the JSON part from the line $jsonPart = $line.Substring(6) try { # Parse the JSON part $parsedJson = $jsonPart | ConvertFrom-Json #Write-LogMessage "Parsed JSON: $jsonPart" -LogFile $logfile if (-not $Chat) { # Extract the text and append it to the complete text - Text Completion $textChunk = $parsedJson.choices[0].text $completeText += $textChunk Write-Host $textChunk -NoNewline } else { # Extract the text and append it to the complete text - Chat Completion $delta = $parsedJson.choices[0].delta.content $completeText += $delta Write-Host $delta -NoNewline } # Update progress indicator #Write-Progress -Activity "Streaming data..." } catch { Write-LogMessage "Error parsing JSON: $_" "ERROR" -LogFile $logfile } } } Write-Host "" $completeText += "`n" if ($VerbosePreference -eq "Continue") { Write-Verbose "Streaming completed. Full text: $completeText" Write-LogMessage "Streaming completed. Full text: $completeText" "VERBOSE" -LogFile $logfile } else { Write-LogMessage "Streaming completed." -LogFile $logfile } # Clean up $reader.Close() $httpClient.Dispose() Write-LogMessage "Resources cleaned up." -LogFile $logfile return $completeText } catch { Write-LogMessage "An error occurred: $_" "ERROR" -LogFile $logfile } } |