-
-
Notifications
You must be signed in to change notification settings - Fork 662
feat: stream responses support #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
In the new commit, replaced custom You can iterate directly through returned generator object: - foreach ($stream->read() as $item) {
+ foreach ($stream as $item) {
$fullText += $item['choices'][0]['text'];
} |
|
@nunomaduro @gehrisandro Any thoughts or suggestions? 💭 |
|
@slavarazum Currently a little bit busy - I will check this as soon as possible. |
|
@slavarazum There is any reason why this makes pull request does not make the client to use stream always? |
|
@nunomaduro The main reason is that the However, in my opinion, stream responses absolutely necessary to provide better user experience if response expects as soon as possible on the client side. |
|
How to make it work now? Is it possible without changes to core code? |
src/Contracts/Transporter.php
Outdated
| * @throws ErrorException|UnserializableResponse|TransporterException | ||
| */ | ||
| public function requestObject(Payload $payload): array; | ||
| public function requestObject(Payload $payload, bool $stream = false): array|Generator; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you somehow, make this method return a single object - for both cases - and have the stream option sent on the Payload object?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created new Response object which returns Transporter::requestObject. Stream option handling moved to Payload object.
src/Resources/Completions.php
Outdated
|
|
||
| private function stream(Generator $stream): Generator | ||
| { | ||
| foreach ($stream as $data) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't you create a single CreateResponse object, that behind the scenes allows to iterate on the stream?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CreateResponse for completions now implements Iterator interface which allows to iterate over the stream. How do you feel about that?
|
@ijjimem Let's wait for the implementation of this PR. Continued working on it. |
- New Transporter Response object which works with raw or stream responses
|
Hi @slavarazum Thank you very much for your work so far. And sorry, for my delayed response. I had a look into your implementation and I can see some good starting points, but nevertheless I would like to make a step back and talk first about the use cases and how the usage should look like. Mainly I can see two use cases or goals to be achieved:
To achieve this two different use cases the user needs a way to use the response in different ways. Therefore I think it's better to have a dedicated Example for use case 1: $stream = $client->completions()->createStreamed([
'model' => 'text-davinci-003',
'prompt' => 'PHP is ',
'max_tokens' => 100,
]); // CreateStreamResponse
return response()->stream(function () use ($stream) {
foreach($stream->iterator() as $newPart){
echo $newPart; // CompletionPartial
}
});Example for use case 2: $stream = $client->completions()->createStreamed([
'model' => 'text-davinci-003',
'prompt' => 'PHP is ',
'max_tokens' => 100,
]); // CreateStreamResponse
while(!$stream->finished()){
$response = $stream->response(); // CreateResponse object with the full completion received so far
sleep(1); // do some work with the response
}Some explanations how I would structure the code:
@nunomaduro and @slavarazum: Can you come up with different use cases and what do you think about having a dedicated method ( |
|
Hi @gehrisandro 🙌 In my vision,
A method that returns the full content of a stream response might makes sense. Let's define the final API.
Single object with $response = $client->completions()->create([
// ...
]);
if ($response->isStream()) {
foreach ($response as $part) {
// ...
}
}vs $response = $client->completions()->create([
// ...
]);
if ($response instanceof CreateStreamResponse) {
foreach ($response->iterator() as $part) {
// ...
}
} |
|
Can I have examples, of our other OpenAI API Clients (in other languages) solved this problem? ps: @slavarazum really super sorry if this issue is taking forever to decide, but currently I am so busy that's been difficult. |
|
@nunomaduro NP 🤝 Me too have some troubles in Ukraine with availability 😅 It's ok to not rush with this to create a truly good implementation. As I can see at first glance, currently it partially solved in other languages. So we can serve as a good example. I have a look around libraries listed in docs and some others. Examples:
Other:
|
|
I tried various combinations with the As far as I was able to see in my tests, if you provide both parameters together it still returns a stream response but it waits sending the stream until the completion is done. What means it is not faster than a request without the stream option. So I would still prefer to have two different methods but I understand your concern that developers probably are going to try the In the opposite we could throw an exception if
@slavarazum Do you still think that having a single method is more convenient? |
|
Looks like separate At the moment I see more use cases for streamed responses than for conventional ones. For reasons of the longer response time, normal requests are more likely to be suitable for background operations when the user is not waiting for a response as soon as possible. Perhaps I'm missing something, since the industry is just emerging. |
|
What is the latest on this? Wold LOVE to be able to start using this for streaming, but can't find a reasonable way of doing it anywhere ... hoping it's soon! |
|
@nunomaduro How do you feel about separate |
|
Any update on this? Would love to see it working :-) I very like the implementation and agree on the single-method approach. |
|
Any update on this PR ? 🙏 |
|
Working on refactoring with |
|
do someone of you deal with this on Laravel with InertiaJs and Vue3? I would love to see your implementation! |
|
oh why not support stream responses? it is so nice to user . it can tell user ai is working .if wait long time user may think ai is not working . |
|
I also hope to support stream as soon as possible. |
|
Working on it exactly right now. Will update a PR with new Chat completions draft as soon as possible. @Pierquinto When it will be finished I will share an examples with client side part. |
|
Let's continue here - #84 |
|
@Pierquinto simple stream reading implementation with async function askAi() {
const response = await fetch("/ask-ai");
const reader = response.body?.pipeThrough(new TextDecoderStream()).getReader();
let delta = await reader.read();
while (!delta.done) {
// do something with the chunk
delta = await reader.read();
}
} |
Brings stream support to return partial progress with server-sent events.
You can think of it as the progress of AI typing.
The main advantage of using streams is that you can return a response to the user much earlier.
See examples above to compare the result.
Request completion model
openai-php-request.mp4
Stream completion model
openai-php-stream.mp4
Currently for
completionsmodel.Usage
Pass the
streamparameter and get a generator object.You can iterate over it until stream ends.
Each iteration of stream read returns the same object as non-streamed responses returns, except
usageandfinishReasonparameters, which are not present in partial streamed responses.Stream text results to the client with Laravel response:
You can create your own event stream (server-sent events) to send partially data to the client.
Example with Laravel response:
The current draft tries to create an implementation with minimal API changes.
array|Generatorusageparameter marked as optional inCreateResponsefinishReasonchanged to nullableHttpTransporterclient dependency changed toGuzzleHttp\ClientInterfaceI would be happy for discussion and bring this PR to a stable version.