Skip to content

Commit ae32070

Browse files
committed
New parameters for streaming and background processing for Get/Request-Response
1 parent 6a22c95 commit ae32070

7 files changed

Lines changed: 289 additions & 35 deletions

File tree

Docs/Get-Response.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ Retrieves a model response with the given ID.
1515
```
1616
Get-Response
1717
[-ResponseId] <String>
18+
[-Include] <String[]>
19+
[-Stream]
20+
[-StreamOutputType <String>]
21+
[-StartingAfter <Int32>]
22+
[-OutputRawResponse]
1823
[-TimeoutSec <Int32>]
1924
[-MaxRetryCount <Int32>]
2025
[-ApiBase <Uri>]
@@ -49,6 +54,55 @@ Position: 0
4954
Accept pipeline input: True (ByPropertyName, ByValue)
5055
```
5156
57+
### -Include
58+
Specify additional output data to include in the model response.
59+
60+
```yaml
61+
Type: String[]
62+
Required: False
63+
Position: Named
64+
```
65+
66+
### -Stream
67+
If set, the model response data will be streamed to the client.
68+
69+
```yaml
70+
Type: SwitchParameter
71+
Required: False
72+
Position: Named
73+
```
74+
75+
### -StreamOutputType
76+
Specifying the format that the function output. This parameter is only valid for the stream output. This parameter is only valid for the stream output.
77+
- `text` : Output only text deltas that the model generated. (Default)
78+
- `object` : Output all events that the API respond.
79+
80+
```yaml
81+
Type: String
82+
Accepted values: text, object
83+
Required: False
84+
Position: Named
85+
Default value: text
86+
```
87+
88+
### -StartingAfter
89+
The sequence number of the event after which to start streaming. This parameter is only valid for the stream output.
90+
91+
```yaml
92+
Type: Int32
93+
Required: False
94+
Position: Named
95+
```
96+
97+
### -OutputRawResponse
98+
If specifies this switch, an output of this function to be a raw response value from the API. (Normally JSON formatted string.)
99+
100+
```yaml
101+
Type: SwitchParameter
102+
Required: False
103+
Position: Named
104+
```
105+
52106
### -TimeoutSec
53107
Specifies how long the request can be pending before it times out.
54108
The default value is `0` (infinite).

Docs/Request-Response.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ Request-Response
6868
[-Temperature <Double>]
6969
[-TopP <Double>]
7070
[-Store <Boolean>]
71+
[-Background]
7172
[-Stream]
7273
[-StreamOutputType <String>]
7374
[-ReasoningEffort <String>]
@@ -639,6 +640,15 @@ Position: Named
639640
Default value: True
640641
```
641642

643+
### -Background
644+
Whether to run the model response in the background. The default is `$false`.
645+
646+
```yaml
647+
Type: SwitchParameter
648+
Required: False
649+
Position: Named
650+
```
651+
642652
### -Stream
643653
If set, the model response data will be streamed to the client.
644654

Private/Invoke-OpenAIAPIRequestSSE.ps1

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,10 @@ function Invoke-OpenAIAPIRequestSSE {
6262
[string]$AuthType = 'openai',
6363

6464
[Parameter()]
65-
[bool]$ReturnRawResponse = $false
65+
[bool]$ReturnRawResponse = $false,
66+
67+
[Parameter()]
68+
[uint64]$First = [uint64]::MaxValue
6669
)
6770

6871
$InternalParams = Initialize-OpenAIAPIRequestParam @PSBoundParameters
@@ -78,6 +81,9 @@ function Invoke-OpenAIAPIRequestSSE {
7881
# Create HttpClient that has 5 min lifetime to reuse
7982
if ($null -eq $script:HttpClientHandler.HttpClient -or $script:HttpClientHandler.Expires -lt [datetime]::Now) {
8083
$script:HttpClientHandler.HttpClient = [System.Net.Http.HttpClient]::new()
84+
if ($TimeoutSec -gt 0) {
85+
$script:HttpClientHandler.HttpClient.Timeout = [TimeSpan]::FromSeconds($TimeoutSec)
86+
}
8187
$script:HttpClientHandler.Expires = [datetime]::Now.AddMinutes(5)
8288
}
8389

@@ -208,14 +214,45 @@ function Invoke-OpenAIAPIRequestSSE {
208214
-Target ($ApiKey, $Organization) -First $startIdx -Last $lastIdx -MaxNumberOfAsterisks 45)
209215
}
210216

211-
while (-not $StreamReader.EndOfStream) {
217+
[uint64]$DataCounter = 0
218+
while ($DataCounter -lt $First) {
212219
$data = $null
213220
#Timeout
214221
$CancelToken.ThrowIfCancellationRequested()
215-
#Retrive response content
216-
$data = [string]$StreamReader.ReadLine()
222+
223+
# Note:
224+
# In some situations, the server may unilaterally close the connection without sending any data.
225+
# To avoid long blocking waits that prevent user cancellation, we use a polling interval.
226+
# We allow a maximum total wait of 30 seconds while checking for cancellation every 500 ms.
227+
$timeoutMs = 30000
228+
$pollInterval = 500 # milliseconds
229+
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
230+
231+
$readTask = $StreamReader.ReadLineAsync()
232+
while (-not $CancelToken.IsCancellationRequested -and $stopwatch.ElapsedMilliseconds -lt $timeoutMs) {
233+
if ($readTask.Wait($pollInterval, $CancelToken)) {
234+
break
235+
}
236+
}
237+
238+
if (-not $readTask.IsCompleted) {
239+
Write-Warning 'Timeout while waiting for response. It seems that the server is not sending any data.'
240+
$cts.Cancel() # Cancel the request
241+
$CancelToken.ThrowIfCancellationRequested()
242+
break
243+
}
244+
245+
# Read data
246+
$data = $readTask.Result
247+
# Check end of stream
248+
if ($null -eq $data) {
249+
Write-Verbose -Message ('End of stream signal received.')
250+
break
251+
}
217252
# Skip on empty
218-
if ([string]::IsNullOrWhiteSpace($data)) { continue }
253+
elseif ([string]::IsNullOrWhiteSpace($data)) {
254+
continue
255+
}
219256
else {
220257
# Debug output
221258
if ($IsDebug) {
@@ -237,17 +274,19 @@ function Invoke-OpenAIAPIRequestSSE {
237274
elseif ($data.StartsWith('data: ', [StringComparison]::Ordinal)) {
238275
# End of stream
239276
if ($data -eq 'data: [DONE]') {
240-
Write-Verbose -Message ('Received the signal of the end of stream')
277+
Write-Verbose -Message ('End of data received.')
241278
break
242279
}
243280
else {
281+
# Increment counter
282+
$DataCounter++
283+
244284
#Output
245285
Write-Output $data.Substring(6) # ("data: ").Length -> 6
246286
}
247287
}
248288
}
249289
}
250-
251290
}
252291
catch [OperationCanceledException] {
253292
# Convert OperationCanceledException to TimeoutException
@@ -268,7 +307,7 @@ function Invoke-OpenAIAPIRequestSSE {
268307
try {
269308
if ($null -ne $cts) { $cts.Dispose() }
270309
if ($null -ne $HttpResponse) { $HttpResponse.Dispose() }
271-
if ($null -ne $ResponseStream) { $ResponseStream.Dispose() }
310+
if ($null -ne $StreamReader) { $StreamReader.Dispose() }
272311
if ($null -ne $RequestMessage) { $RequestMessage.Dispose() }
273312
}
274313
catch {}

Public/Responses/Get-Response.ps1

Lines changed: 91 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,27 @@ function Get-Response {
1010
[Alias('response_id')]
1111
[string][UrlEncodeTransformation()]$ResponseId,
1212

13+
[Parameter()]
14+
[Completions('file_search_call.results', 'message.input_image.image_url', 'computer_call_output.output.image_url', 'reasoning.encrypted_content')]
15+
[AllowEmptyCollection()]
16+
[string[]]$Include,
17+
18+
#region Stream
19+
[Parameter()]
20+
[switch]$Stream = $false,
21+
22+
[Parameter()]
23+
[ValidateSet('text', 'object')]
24+
[string]$StreamOutputType = 'text',
25+
26+
[Parameter()]
27+
[Alias('starting_after')]
28+
[int]$StartingAfter,
29+
#endregion Stream
30+
31+
[Parameter()]
32+
[switch]$OutputRawResponse,
33+
1334
# For internal use
1435
[Parameter(DontShow)]
1536
[switch]$Primitive,
@@ -63,11 +84,26 @@ function Get-Response {
6384
#region Construct Query URI
6485
$UriBuilder = [System.UriBuilder]::new($OpenAIParameter.Uri)
6586
$UriBuilder.Path += "/$ResponseId"
87+
$QueryParam = [System.Web.HttpUtility]::ParseQueryString($UriBuilder.Query)
88+
89+
if ($Stream) {
90+
$QueryParam.Add('stream', ($Stream.ToString().ToLowerInvariant()))
91+
}
92+
if ($PSBoundParameters.ContainsKey('StartingAfter')) {
93+
$QueryParam.Add('starting_after', $StartingAfter)
94+
}
95+
if ($PSBoundParameters.ContainsKey('Include')) {
96+
foreach ($IncludeItem in $Include) {
97+
$QueryParam.Add('include[]', $IncludeItem)
98+
}
99+
}
100+
101+
$UriBuilder.Query = $QueryParam.ToString()
66102
$QueryUri = $UriBuilder.Uri
67103
#endregion
68104

69105
#region Send API Request
70-
$params = @{
106+
$splat = @{
71107
Method = 'Get'
72108
Uri = $QueryUri
73109
# ContentType = $OpenAIParameter.ContentType
@@ -80,21 +116,65 @@ function Get-Response {
80116
AdditionalHeaders = $AdditionalHeaders
81117
AdditionalBody = $AdditionalBody
82118
}
83-
$Response = Invoke-OpenAIAPIRequest @params
84119

85-
# error check
86-
if ($null -eq $Response) {
120+
#region Send API Request (Stream)
121+
if ($Stream) {
122+
# Stream output
123+
Invoke-OpenAIAPIRequestSSE @splat |
124+
Where-Object {
125+
-not [string]::IsNullOrEmpty($_)
126+
} | ForEach-Object -Process {
127+
if ($OutputRawResponse) {
128+
Write-Output $_
129+
}
130+
else {
131+
# Parse response object
132+
try {
133+
$Response = $_ | ConvertFrom-Json -ErrorAction Stop
134+
}
135+
catch {
136+
Write-Error -Exception $_.Exception
137+
}
138+
139+
if ($StreamOutputType -eq 'text') {
140+
if ($Response.type -cne 'response.output_text.delta') {
141+
continue
142+
}
143+
Write-Output $Response.delta
144+
}
145+
else {
146+
Write-Output $Response
147+
}
148+
}
149+
}
150+
87151
return
88152
}
89153
#endregion
90154

91-
#region Parse response object
92-
try {
93-
$Response = $Response | ConvertFrom-Json -ErrorAction Stop
94-
}
95-
catch {
96-
Write-Error -Exception $_.Exception
97-
return
155+
#region Send API Request (No Stream)
156+
else {
157+
$Response = Invoke-OpenAIAPIRequest @splat
158+
159+
# error check
160+
if ($null -eq $Response) {
161+
return
162+
}
163+
#endregion
164+
165+
#region Parse response object
166+
if ($OutputRawResponse) {
167+
Write-Output $Response
168+
return
169+
}
170+
try {
171+
$Response = $Response | ConvertFrom-Json -ErrorAction Stop
172+
}
173+
catch {
174+
Write-Error -Exception $_.Exception
175+
return
176+
}
177+
#endregion
98178
}
99179
#endregion
100180

0 commit comments

Comments
 (0)