Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
chenpangpang
open-webui
Commits
efa258c6
Commit
efa258c6
authored
Apr 20, 2024
by
Jun Siang Cheah
Browse files
feat: split large openai responses into smaller chunkers
parent
a4083f43
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
90 additions
and
55 deletions
+90
-55
src/lib/apis/streaming/index.ts
src/lib/apis/streaming/index.ts
+65
-0
src/routes/(app)/+page.svelte
src/routes/(app)/+page.svelte
+11
-26
src/routes/(app)/c/[id]/+page.svelte
src/routes/(app)/c/[id]/+page.svelte
+14
-29
No files found.
src/lib/apis/streaming/index.ts
0 → 100644
View file @
efa258c6
type
TextStreamUpdate
=
{
done
:
boolean
;
value
:
string
;
};
// createOpenAITextStream takes a ReadableStreamDefaultReader from an SSE response,
// and returns an async generator that emits delta updates with large deltas chunked into random sized chunks
export
async
function
createOpenAITextStream
(
messageStream
:
ReadableStreamDefaultReader
):
Promise
<
AsyncGenerator
<
TextStreamUpdate
>>
{
return
streamLargeDeltasAsRandomChunks
(
openAIStreamToIterator
(
messageStream
));
}
async
function
*
openAIStreamToIterator
(
reader
:
ReadableStreamDefaultReader
):
AsyncGenerator
<
TextStreamUpdate
>
{
while
(
true
)
{
const
{
value
,
done
}
=
await
reader
.
read
();
if
(
done
)
{
yield
{
done
:
true
,
value
:
''
};
break
;
}
const
lines
=
value
.
split
(
'
\n
'
);
for
(
const
line
of
lines
)
{
if
(
line
!==
''
)
{
console
.
log
(
line
);
if
(
line
===
'
data: [DONE]
'
)
{
yield
{
done
:
true
,
value
:
''
};
}
else
{
const
data
=
JSON
.
parse
(
line
.
replace
(
/^data: /
,
''
));
console
.
log
(
data
);
yield
{
done
:
false
,
value
:
data
.
choices
[
0
].
delta
.
content
??
''
};
}
}
}
}
}
// streamLargeDeltasAsRandomChunks will chunk large deltas (length > 5) into random sized chunks between 1-3 characters
// This is to simulate a more fluid streaming, even though some providers may send large chunks of text at once
async
function
*
streamLargeDeltasAsRandomChunks
(
iterator
:
AsyncGenerator
<
TextStreamUpdate
>
):
AsyncGenerator
<
TextStreamUpdate
>
{
for
await
(
const
textStreamUpdate
of
iterator
)
{
if
(
textStreamUpdate
.
done
)
{
yield
textStreamUpdate
;
return
;
}
let
content
=
textStreamUpdate
.
value
;
if
(
content
.
length
<
5
)
{
yield
{
done
:
false
,
value
:
content
};
continue
;
}
while
(
content
!=
''
)
{
const
chunkSize
=
Math
.
min
(
Math
.
floor
(
Math
.
random
()
*
3
)
+
1
,
content
.
length
);
const
chunk
=
content
.
slice
(
0
,
chunkSize
);
yield
{
done
:
false
,
value
:
chunk
};
await
sleep
(
5
);
content
=
content
.
slice
(
chunkSize
);
}
}
}
const
sleep
=
(
ms
:
number
)
=>
new
Promise
((
resolve
)
=>
setTimeout
(
resolve
,
ms
));
src/routes/(app)/+page.svelte
View file @
efa258c6
...
@@ -39,6 +39,7 @@
...
@@ -39,6 +39,7 @@
import
{
RAGTemplate
}
from
'$lib/utils/rag'
;
import
{
RAGTemplate
}
from
'$lib/utils/rag'
;
import
{
LITELLM_API_BASE_URL
,
OLLAMA_API_BASE_URL
,
OPENAI_API_BASE_URL
}
from
'$lib/constants'
;
import
{
LITELLM_API_BASE_URL
,
OLLAMA_API_BASE_URL
,
OPENAI_API_BASE_URL
}
from
'$lib/constants'
;
import
{
WEBUI_BASE_URL
}
from
'$lib/constants'
;
import
{
WEBUI_BASE_URL
}
from
'$lib/constants'
;
import
{
createOpenAITextStream
}
from
'$lib/apis/streaming'
;
const
i18n
=
getContext
(
'i18n'
);
const
i18n
=
getContext
(
'i18n'
);
...
@@ -599,39 +600,23 @@
...
@@ -599,39 +600,23 @@
.
pipeThrough
(
splitStream
(
'\n'
))
.
pipeThrough
(
splitStream
(
'\n'
))
.
getReader
();
.
getReader
();
while
(
true
)
{
const
textStream
=
await
createOpenAITextStream
(
reader
);
const
{
value
,
done
}
=
await
reader
.
read
();
console
.
log
(
textStream
);
for
await
(
const
update
of
textStream
)
{
const
{
value
,
done
}
=
update
;
if
(
done
||
stopResponseFlag
||
_chatId
!== $chatId) {
if
(
done
||
stopResponseFlag
||
_chatId
!== $chatId) {
responseMessage
.
done
=
true
;
responseMessage
.
done
=
true
;
messages
=
messages
;
messages
=
messages
;
break
;
break
;
}
}
try
{
if
(
responseMessage
.
content
==
''
&&
value
==
'\n'
)
{
let
lines
=
value
.
split
(
'\n'
);
for
(
const
line
of
lines
)
{
if
(
line
!== '') {
console
.
log
(
line
);
if
(
line
===
'data: [DONE]'
)
{
responseMessage
.
done
=
true
;
messages
=
messages
;
}
else
{
let
data
=
JSON
.
parse
(
line
.
replace
(/^
data
:
/,
''
));
console
.
log
(
data
);
if
(
responseMessage
.
content
==
''
&&
data
.
choices
[
0
].
delta
.
content
==
'\n'
)
{
continue
;
continue
;
}
else
{
}
else
{
responseMessage
.
content
+=
data
.
choices
[
0
].
delta
.
content
??
''
;
responseMessage
.
content
+=
value
;
messages
=
messages
;
messages
=
messages
;
}
}
}
}
}
}
catch
(
error
)
{
console
.
log
(
error
);
}
if
($
settings
.
notificationEnabled
&&
!document.hasFocus()) {
if
($
settings
.
notificationEnabled
&&
!document.hasFocus()) {
const
notification
=
new
Notification
(`
OpenAI
${
model
}`,
{
const
notification
=
new
Notification
(`
OpenAI
${
model
}`,
{
...
...
src/routes/(app)/c/[id]/+page.svelte
View file @
efa258c6
...
@@ -42,6 +42,7 @@
...
@@ -42,6 +42,7 @@
OLLAMA_API_BASE_URL
,
OLLAMA_API_BASE_URL
,
WEBUI_BASE_URL
WEBUI_BASE_URL
}
from
'$lib/constants'
;
}
from
'$lib/constants'
;
import
{
createOpenAITextStream
}
from
'$lib/apis/streaming'
;
const
i18n
=
getContext
(
'i18n'
);
const
i18n
=
getContext
(
'i18n'
);
...
@@ -611,39 +612,23 @@
...
@@ -611,39 +612,23 @@
.
pipeThrough
(
splitStream
(
'\n'
))
.
pipeThrough
(
splitStream
(
'\n'
))
.
getReader
();
.
getReader
();
while
(
true
)
{
const
textStream
=
await
createOpenAITextStream
(
reader
);
const
{
value
,
done
}
=
await
reader
.
read
();
console
.
log
(
textStream
);
for
await
(
const
update
of
textStream
)
{
const
{
value
,
done
}
=
update
;
if
(
done
||
stopResponseFlag
||
_chatId
!== $chatId) {
if
(
done
||
stopResponseFlag
||
_chatId
!== $chatId) {
responseMessage
.
done
=
true
;
responseMessage
.
done
=
true
;
messages
=
messages
;
messages
=
messages
;
break
;
break
;
}
}
try
{
if
(
responseMessage
.
content
==
''
&&
value
==
'\n'
)
{
let
lines
=
value
.
split
(
'\n'
);
for
(
const
line
of
lines
)
{
if
(
line
!== '') {
console
.
log
(
line
);
if
(
line
===
'data: [DONE]'
)
{
responseMessage
.
done
=
true
;
messages
=
messages
;
}
else
{
let
data
=
JSON
.
parse
(
line
.
replace
(/^
data
:
/,
''
));
console
.
log
(
data
);
if
(
responseMessage
.
content
==
''
&&
data
.
choices
[
0
].
delta
.
content
==
'\n'
)
{
continue
;
continue
;
}
else
{
}
else
{
responseMessage
.
content
+=
data
.
choices
[
0
].
delta
.
content
??
''
;
responseMessage
.
content
+=
value
;
messages
=
messages
;
messages
=
messages
;
}
}
}
}
}
}
catch
(
error
)
{
console
.
log
(
error
);
}
if
($
settings
.
notificationEnabled
&&
!document.hasFocus()) {
if
($
settings
.
notificationEnabled
&&
!document.hasFocus()) {
const
notification
=
new
Notification
(`
OpenAI
${
model
}`,
{
const
notification
=
new
Notification
(`
OpenAI
${
model
}`,
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment