feat: add ability to override global "fetch"

pull/346/head
Travis Fischer 2023-02-14 00:30:06 -06:00
rodzic 0fc6575b80
commit 1bffd5e92a
4 zmienionych plików z 52 dodań i 35 usunięć

Wyświetl plik

@ -5,7 +5,7 @@ import QuickLRU from 'quick-lru'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import * as types from './types' import * as types from './types'
import { fetch } from './fetch' import { fetch as globalFetch } from './fetch'
import { fetchSSE } from './fetch-sse' import { fetchSSE } from './fetch-sse'
// Official model (costs money and is not fine-tuned for chat) // Official model (costs money and is not fine-tuned for chat)
@ -27,6 +27,7 @@ export class ChatGPTAPI {
protected _assistantLabel: string protected _assistantLabel: string
protected _endToken: string protected _endToken: string
protected _sepToken: string protected _sepToken: string
protected _fetch: types.FetchFn
protected _getMessageById: types.GetMessageByIdFunction protected _getMessageById: types.GetMessageByIdFunction
protected _upsertMessage: types.UpsertMessageFunction protected _upsertMessage: types.UpsertMessageFunction
@ -47,6 +48,7 @@ export class ChatGPTAPI {
* @param messageStore - Optional [Keyv](https://github.com/jaredwray/keyv) store to persist chat messages to. If not provided, messages will be lost when the process exits. * @param messageStore - Optional [Keyv](https://github.com/jaredwray/keyv) store to persist chat messages to. If not provided, messages will be lost when the process exits.
* @param getMessageById - Optional function to retrieve a message by its ID. If not provided, the default implementation will be used (using an in-memory `messageStore`). * @param getMessageById - Optional function to retrieve a message by its ID. If not provided, the default implementation will be used (using an in-memory `messageStore`).
* @param upsertMessage - Optional function to insert or update a message. If not provided, the default implementation will be used (using an in-memory `messageStore`). * @param upsertMessage - Optional function to insert or update a message. If not provided, the default implementation will be used (using an in-memory `messageStore`).
* @param fetch - Optional override for the `fetch` implementation to use. Defaults to the global `fetch` function.
*/ */
constructor(opts: { constructor(opts: {
apiKey: string apiKey: string
@ -77,6 +79,8 @@ export class ChatGPTAPI {
messageStore?: Keyv messageStore?: Keyv
getMessageById?: types.GetMessageByIdFunction getMessageById?: types.GetMessageByIdFunction
upsertMessage?: types.UpsertMessageFunction upsertMessage?: types.UpsertMessageFunction
fetch?: types.FetchFn
}) { }) {
const { const {
apiKey, apiKey,
@ -90,13 +94,15 @@ export class ChatGPTAPI {
userLabel = USER_LABEL_DEFAULT, userLabel = USER_LABEL_DEFAULT,
assistantLabel = ASSISTANT_LABEL_DEFAULT, assistantLabel = ASSISTANT_LABEL_DEFAULT,
getMessageById = this._defaultGetMessageById, getMessageById = this._defaultGetMessageById,
upsertMessage = this._defaultUpsertMessage upsertMessage = this._defaultUpsertMessage,
fetch = globalFetch
} = opts } = opts
this._apiKey = apiKey this._apiKey = apiKey
this._apiBaseUrl = apiBaseUrl this._apiBaseUrl = apiBaseUrl
this._apiReverseProxyUrl = apiReverseProxyUrl this._apiReverseProxyUrl = apiReverseProxyUrl
this._debug = !!debug this._debug = !!debug
this._fetch = fetch
this._completionParams = { this._completionParams = {
model: CHATGPT_MODEL, model: CHATGPT_MODEL,
@ -141,6 +147,14 @@ export class ChatGPTAPI {
if (!this._apiKey) { if (!this._apiKey) {
throw new Error('ChatGPT invalid apiKey') throw new Error('ChatGPT invalid apiKey')
} }
if (!this._fetch) {
throw new Error('Invalid environment; fetch is not defined')
}
if (typeof this._fetch !== 'function') {
throw new Error('Invalid "fetch" is not a function')
}
} }
/** /**
@ -229,40 +243,44 @@ export class ChatGPTAPI {
} }
if (stream) { if (stream) {
fetchSSE(url, { fetchSSE(
method: 'POST', url,
headers, {
body: JSON.stringify(body), method: 'POST',
signal: abortSignal, headers,
onMessage: (data: string) => { body: JSON.stringify(body),
if (data === '[DONE]') { signal: abortSignal,
result.text = result.text.trim() onMessage: (data: string) => {
return resolve(result) if (data === '[DONE]') {
} result.text = result.text.trim()
return resolve(result)
try {
const response: types.openai.CompletionResponse =
JSON.parse(data)
if (response.id) {
result.id = response.id
} }
if (response?.choices?.length) { try {
result.text += response.choices[0].text const response: types.openai.CompletionResponse =
result.detail = response JSON.parse(data)
onProgress?.(result) if (response.id) {
result.id = response.id
}
if (response?.choices?.length) {
result.text += response.choices[0].text
result.detail = response
onProgress?.(result)
}
} catch (err) {
console.warn('ChatGPT stream SEE event unexpected error', err)
return reject(err)
} }
} catch (err) {
console.warn('ChatGPT stream SEE event unexpected error', err)
return reject(err)
} }
} },
}).catch(reject) this._fetch
).catch(reject)
} else { } else {
try { try {
const res = await fetch(url, { const res = await this._fetch(url, {
method: 'POST', method: 'POST',
headers, headers,
body: JSON.stringify(body), body: JSON.stringify(body),

Wyświetl plik

@ -1,12 +1,13 @@
import { createParser } from 'eventsource-parser' import { createParser } from 'eventsource-parser'
import * as types from './types' import * as types from './types'
import { fetch } from './fetch' import { fetch as globalFetch } from './fetch'
import { streamAsyncIterable } from './stream-async-iterable' import { streamAsyncIterable } from './stream-async-iterable'
export async function fetchSSE( export async function fetchSSE(
url: string, url: string,
options: Parameters<typeof fetch>[1] & { onMessage: (data: string) => void } options: Parameters<typeof fetch>[1] & { onMessage: (data: string) => void },
fetch: types.FetchFn = globalFetch
) { ) {
const { onMessage, ...fetchOptions } = options const { onMessage, ...fetchOptions } = options
const res = await fetch(url, fetchOptions) const res = await fetch(url, fetchOptions)

Wyświetl plik

@ -2,8 +2,4 @@
const fetch = globalThis.fetch const fetch = globalThis.fetch
if (typeof fetch !== 'function') {
throw new Error('Invalid environment: global fetch not defined')
}
export { fetch } export { fetch }

Wyświetl plik

@ -1,5 +1,7 @@
export type Role = 'user' | 'assistant' export type Role = 'user' | 'assistant'
export type FetchFn = typeof fetch
export type SendMessageOptions = { export type SendMessageOptions = {
conversationId?: string conversationId?: string
parentMessageId?: string parentMessageId?: string