Skip to content

Commit 544b2e6

Browse files
authored
Merge pull request #42 from srcnalt/feature/response-streaming
Support for text stream responses.
2 parents 5924018 + a4df3b7 commit 544b2e6

File tree

10 files changed

+1140
-7
lines changed

10 files changed

+1140
-7
lines changed

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,30 @@ private async void SendRequest()
6666
}
6767
```
6868

69+
To make a stream request, you can use the `CreateCompletionAsync` and `CreateChatCompletionAsync` methods.
70+
These methods are going to set `Stream` property of the request to `true` and return responses through an onResponse callback.
71+
In this case text responses are stored in `Delta` property of the `Choices` field.
72+
73+
```csharp
74+
var req = new CreateCompletionRequest{
75+
Model = "text-davinci-003",
76+
Prompt = "Say this is a test.",
77+
MaxTokens = 7,
78+
Temperature = 0
79+
};
80+
81+
openai.CreateCompletionAsync(req,
82+
(responses) => {
83+
var result = string.Join("", responses.Select(response => response.Choices[0].Delta.Content));
84+
Debug.Log(result);
85+
},
86+
() => {
87+
Debug.Log("completed");
88+
},
89+
new CancellationTokenSource());
90+
}
91+
```
92+
6993
### Sample Projects
7094
This package includes two sample scenes that you can import via the Package Manager:
7195

Runtime/DataTypes.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ public sealed class CreateChatCompletionRequest
8383
public string Model { get; set; }
8484
public List<ChatMessage> Messages { get; set; }
8585
public float? Temperature { get; set; } = 1;
86-
public int? N { get; set; } = 1;
87-
public bool? Stream { get; set; } = false;
86+
public int N { get; set; } = 1;
87+
public bool Stream { get; set; } = false;
8888
public string Stop { get; set; }
8989
public int? MaxTokens { get; set; }
9090
public float? PresencePenalty { get; set; } = 0;
@@ -107,6 +107,7 @@ public struct CreateChatCompletionResponse : IResponse
107107
public struct ChatChoice
108108
{
109109
public ChatMessage Message { get; set; }
110+
public ChatMessage Delta { get; set; }
110111
public int? Index { get; set; }
111112
public string FinishReason { get; set; }
112113
}
@@ -116,6 +117,7 @@ public struct ChatMessage
116117
public string Role { get; set; }
117118
public string Content { get; set; }
118119
}
120+
119121
#endregion
120122

121123
#region Audio Transcriptions Data Types
@@ -159,8 +161,8 @@ public sealed class CreateCompletionRequest
159161
public int? MaxTokens { get; set; } = 16;
160162
public float? Temperature { get; set; } = 1;
161163
public float? TopP { get; set; } = 1;
162-
public int? N { get; set; } = 1;
163-
public bool? Stream { get; set; } = false;
164+
public int N { get; set; } = 1;
165+
public bool Stream { get; set; } = false;
164166
public int? Logpropbs { get; set; }
165167
public bool? Echo { get; set; } = false;
166168
public string Stop { get; set; }

Runtime/OpenAIApi.cs

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1+
using System;
2+
using System.IO;
13
using UnityEngine;
24
using System.Text;
35
using Newtonsoft.Json;
6+
using System.Threading;
47
using System.Globalization;
58
using System.Threading.Tasks;
69
using UnityEngine.Networking;
710
using System.Collections.Generic;
8-
using System.IO;
911
using Newtonsoft.Json.Serialization;
12+
using System.Linq;
1013

1114
namespace OpenAI
1215
{
@@ -87,6 +90,61 @@ private async Task<T> DispatchRequest<T>(string path, string method, byte[] payl
8790

8891
return data;
8992
}
93+
94+
/// <summary>
95+
/// Dispatches an HTTP request to the specified path with the specified method and optional payload.
96+
/// </summary>
97+
/// <param name="path">The path to send the request to.</param>
98+
/// <param name="method">The HTTP method to use for the request.</param>
99+
/// <param name="onResponse">A callback function to be called when a response is updated.</param>
100+
/// <param name="onComplete">A callback function to be called when the request is complete.</param>
101+
/// <param name="token">A cancellation token to cancel the request.</param>
102+
/// <param name="payload">An optional byte array of json payload to include in the request.</param>
103+
private async void DispatchRequest<T>(string path, string method, Action<List<T>> onResponse, Action onComplete, CancellationTokenSource token, byte[] payload = null) where T: IResponse
104+
{
105+
using (var request = UnityWebRequest.Put(path, payload))
106+
{
107+
request.method = method;
108+
request.SetHeaders(Configuration, ContentType.ApplicationJson);
109+
110+
var asyncOperation = request.SendWebRequest();
111+
112+
do
113+
{
114+
List<T> dataList = new List<T>();
115+
string[] lines = request.downloadHandler.text.Split('\n').Where(line => line != "").ToArray();
116+
117+
foreach (string line in lines)
118+
{
119+
var value = line.Replace("data: ", "");
120+
121+
if (value.Contains("[DONE]"))
122+
{
123+
onComplete?.Invoke();
124+
break;
125+
}
126+
127+
var data = JsonConvert.DeserializeObject<T>(value, jsonSerializerSettings);
128+
129+
if (data?.Error != null)
130+
{
131+
ApiError error = data.Error;
132+
Debug.LogError($"Error Message: {error.Message}\nError Type: {error.Type}\n");
133+
}
134+
else
135+
{
136+
dataList.Add(data);
137+
}
138+
}
139+
onResponse?.Invoke(dataList);
140+
141+
await Task.Yield();
142+
}
143+
while (!asyncOperation.isDone && !token.IsCancellationRequested);
144+
145+
onComplete?.Invoke();
146+
}
147+
}
90148

91149
/// <summary>
92150
/// Dispatches an HTTP request to the specified path with a multi-part data form.
@@ -167,6 +225,22 @@ public async Task<CreateCompletionResponse> CreateCompletion(CreateCompletionReq
167225
return await DispatchRequest<CreateCompletionResponse>(path, UnityWebRequest.kHttpVerbPOST, payload);
168226
}
169227

228+
/// <summary>
229+
/// Creates a chat completion request as in ChatGPT.
230+
/// </summary>
231+
/// <param name="request">See <see cref="CreateChatCompletionRequest"/></param>
232+
/// <param name="onResponse">Callback function that will be called when stream response is updated.</param>
233+
/// <param name="onComplete">Callback function that will be called when stream response is completed.</param>
234+
/// <param name="token">Cancellation token to cancel the request.</param>
235+
public void CreateCompletionAsync(CreateCompletionRequest request, Action<List<CreateCompletionResponse>> onResponse, Action onComplete, CancellationTokenSource token)
236+
{
237+
request.Stream = true;
238+
var path = $"{BASE_PATH}/completions";
239+
var payload = CreatePayload(request);
240+
241+
DispatchRequest(path, UnityWebRequest.kHttpVerbPOST, onResponse, onComplete, token, payload);
242+
}
243+
170244
/// <summary>
171245
/// Creates a chat completion request as in ChatGPT.
172246
/// </summary>
@@ -176,9 +250,26 @@ public async Task<CreateChatCompletionResponse> CreateChatCompletion(CreateChatC
176250
{
177251
var path = $"{BASE_PATH}/chat/completions";
178252
var payload = CreatePayload(request);
253+
179254
return await DispatchRequest<CreateChatCompletionResponse>(path, UnityWebRequest.kHttpVerbPOST, payload);
180255
}
181256

257+
/// <summary>
258+
/// Creates a chat completion request as in ChatGPT.
259+
/// </summary>
260+
/// <param name="request">See <see cref="CreateChatCompletionRequest"/></param>
261+
/// <param name="onResponse">Callback function that will be called when stream response is updated.</param>
262+
/// <param name="onComplete">Callback function that will be called when stream response is completed.</param>
263+
/// <param name="token">Cancellation token to cancel the request.</param>
264+
public void CreateChatCompletionAsync(CreateChatCompletionRequest request, Action<List<CreateChatCompletionResponse>> onResponse, Action onComplete, CancellationTokenSource token)
265+
{
266+
request.Stream = true;
267+
var path = $"{BASE_PATH}/chat/completions";
268+
var payload = CreatePayload(request);
269+
270+
DispatchRequest(path, UnityWebRequest.kHttpVerbPOST, onResponse, onComplete, token, payload);
271+
}
272+
182273
/// <summary>
183274
/// Creates a new edit for the provided input, instruction, and parameters.
184275
/// </summary>

Samples~/Stream Response.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)