Skip to content

Commit

Permalink
fix: Set stream: true when decoding streamed chunks (#182)
Browse files Browse the repository at this point in the history
  • Loading branch information
shuding committed Jun 21, 2023
1 parent 92da981 commit 23f0899
Show file tree
Hide file tree
Showing 10 changed files with 46 additions and 22 deletions.
5 changes: 5 additions & 0 deletions .changeset/lazy-ligers-shave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'ai': patch
---

Set stream: true when decoding streamed chunks
5 changes: 3 additions & 2 deletions packages/core/react/use-chat.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useCallback, useId, useRef, useEffect, useState } from 'react'
import useSWRMutation from 'swr/mutation'
import useSWR from 'swr'
import { nanoid, decodeAIStreamChunk } from '../shared/utils'
import { nanoid, createChunkDecoder } from '../shared/utils'

import type { Message, CreateMessage, UseChatOptions } from '../shared/types'
export type { Message, CreateMessage, UseChatOptions }
Expand Down Expand Up @@ -154,14 +154,15 @@ export function useChat({
const createdAt = new Date()
const replyId = nanoid()
const reader = res.body.getReader()
const decode = createChunkDecoder()

while (true) {
const { done, value } = await reader.read()
if (done) {
break
}
// Update the chat state with the new message tokens.
result += decodeAIStreamChunk(value)
result += decode(value)
mutate(
[
...messagesSnapshot,
Expand Down
5 changes: 3 additions & 2 deletions packages/core/react/use-completion.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useId, useRef, useState } from 'react'
import useSWRMutation from 'swr/mutation'
import useSWR from 'swr'
import { decodeAIStreamChunk } from '../shared/utils'
import { createChunkDecoder } from '../shared/utils'
import { UseCompletionOptions } from '../shared/types'

export type UseCompletionHelpers = {
Expand Down Expand Up @@ -136,6 +136,7 @@ export function useCompletion({

let result = ''
const reader = res.body.getReader()
const decoder = createChunkDecoder()

while (true) {
const { done, value } = await reader.read()
Expand All @@ -144,7 +145,7 @@ export function useCompletion({
}

// Update the completion state with the new message tokens.
result += decodeAIStreamChunk(value)
result += decoder(value)
mutate(result, false)

// The request has been aborted, stop reading the stream.
Expand Down
11 changes: 11 additions & 0 deletions packages/core/shared/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { createChunkDecoder } from './utils'

describe('utils', () => {
it('correctly decode streamed utf8 chunks', () => {
const decoder = createChunkDecoder()
// Without `stream: true` this will fail.
const chunk1 = new Uint8Array([226, 153])
const chunk2 = new Uint8Array([165])
expect(decoder(chunk1) + decoder(chunk2)).toBe('♥')
})
})
8 changes: 5 additions & 3 deletions packages/core/shared/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ export const nanoid = customAlphabet(
7
)

const decoder = new TextDecoder()
export function decodeAIStreamChunk(chunk: Uint8Array): string {
return decoder.decode(chunk)
export function createChunkDecoder() {
const decoder = new TextDecoder()
return function (chunk: Uint8Array): string {
return decoder.decode(chunk, { stream: true })
}
}
7 changes: 4 additions & 3 deletions packages/core/svelte/use-chat.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useSWR } from 'sswr'
import { Readable, get, readable, writable } from 'svelte/store'
import { Readable, get, writable } from 'svelte/store'

import type { Message, CreateMessage, UseChatOptions } from '../shared/types'
import { Writable } from 'svelte/store'
import { decodeAIStreamChunk, nanoid } from '../shared/utils'
import { nanoid, createChunkDecoder } from '../shared/utils'

export type { Message, CreateMessage, UseChatOptions }

Expand Down Expand Up @@ -134,14 +134,15 @@ export function useChat({
const createdAt = new Date()
const replyId = nanoid()
const reader = res.body.getReader()
const decoder = createChunkDecoder()

while (true) {
const { done, value } = await reader.read()
if (done) {
break
}
// Update the chat state with the new message tokens.
result += decodeAIStreamChunk(value)
result += decoder(value)
mutate([
...messagesSnapshot,
{
Expand Down
5 changes: 3 additions & 2 deletions packages/core/svelte/use-completion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useSWR } from 'sswr'
import { Readable, get, writable } from 'svelte/store'

import { Writable } from 'svelte/store'
import { decodeAIStreamChunk } from '../shared/utils'
import { createChunkDecoder } from '../shared/utils'
import { UseCompletionOptions } from '../shared/types'

export type UseCompletionHelpers = {
Expand Down Expand Up @@ -116,14 +116,15 @@ export function useCompletion({

let result = ''
const reader = res.body.getReader()
const decoder = createChunkDecoder()

while (true) {
const { done, value } = await reader.read()
if (done) {
break
}
// Update the chat state with the new message tokens.
result += decodeAIStreamChunk(value)
result += decoder(value)
mutate(result)

// The request has been aborted, stop reading the stream.
Expand Down
5 changes: 3 additions & 2 deletions packages/core/vue/use-chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ref } from 'vue'
import type { Ref } from 'vue'

import type { Message, CreateMessage, UseChatOptions } from '../shared/types'
import { decodeAIStreamChunk, nanoid } from '../shared/utils'
import { createChunkDecoder, nanoid } from '../shared/utils'

export type { Message, CreateMessage, UseChatOptions }

Expand Down Expand Up @@ -136,14 +136,15 @@ export function useChat({
const createdAt = new Date()
const replyId = nanoid()
const reader = res.body.getReader()
const decoder = createChunkDecoder()

while (true) {
const { done, value } = await reader.read()
if (done) {
break
}
// Update the chat state with the new message tokens.
result += decodeAIStreamChunk(value)
result += decoder(value)
mutate([
...messagesSnapshot,
{
Expand Down
5 changes: 3 additions & 2 deletions packages/core/vue/use-completion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import swrv from 'swrv'
import { ref } from 'vue'
import type { Ref } from 'vue'

import { decodeAIStreamChunk } from '../shared/utils'
import { createChunkDecoder } from '../shared/utils'
import { UseCompletionOptions } from '../shared/types'

export type UseCompletionHelpers = {
Expand Down Expand Up @@ -119,14 +119,15 @@ export function useCompletion({

let result = ''
const reader = res.body.getReader()
const decoder = createChunkDecoder()

while (true) {
const { done, value } = await reader.read()
if (done) {
break
}
// Update the chat state with the new message tokens.
result += decodeAIStreamChunk(value)
result += decoder(value)
mutate(result)

// The request has been aborted, stop reading the stream.
Expand Down
12 changes: 6 additions & 6 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 23f0899

Please sign in to comment.