(
prompt: ChatGPTMessage[],
params: ChatGPTParams,
model: string,
shouldTerminate: (rawOutput: string) => boolean,
handleStreamingToUser: (transformed: string, rawOutput: string) => void,
promptName: string, // For logs
initialRawOutput: string = "",
placeholderToOriginalMap: Record<string, string> = {},
)
| 9 | import { replacePlaceholdersDuringStreaming } from "../../edge-runtime/angelaUtils"; |
| 10 | |
| 11 | export async function streamWithEarlyTermination( |
| 12 | prompt: ChatGPTMessage[], |
| 13 | params: ChatGPTParams, |
| 14 | model: string, |
| 15 | shouldTerminate: (rawOutput: string) => boolean, |
| 16 | handleStreamingToUser: (transformed: string, rawOutput: string) => void, |
| 17 | promptName: string, // For logs |
| 18 | initialRawOutput: string = "", |
| 19 | placeholderToOriginalMap: Record<string, string> = {}, |
| 20 | ): Promise<{ raw: string; transformed: string } | null> { |
| 21 | /** IMPORTANT: The function outputs the raw message, the handle streaming |
| 22 | * function has both raw and transformed outputs. |
| 23 | * null output means there's been an error **/ |
| 24 | const startTime = Date.now(); |
| 25 | let res = await exponentialRetryWrapper( |
| 26 | streamLLMResponse, |
| 27 | [prompt, params, model], |
| 28 | 3, |
| 29 | ); |
| 30 | if (res === null || "message" in res) { |
| 31 | console.error( |
| 32 | `${promptName} LLM API call failed. The error was: ${JSON.stringify( |
| 33 | res, |
| 34 | )}`, |
| 35 | ); |
| 36 | return null; |
| 37 | } |
| 38 | |
| 39 | // Stream response chunk by chunk |
| 40 | const decoder = new TextDecoder(); |
| 41 | const reader = res.getReader(); |
| 42 | |
| 43 | let rawOutput = initialRawOutput, |
| 44 | transformedOutput = initialRawOutput, |
| 45 | done = false, |
| 46 | incompleteChunk = "", |
| 47 | first = true; |
| 48 | let placeholderBuffer = ""; |
| 49 | const usingPlaceholderMap = Object.keys(placeholderToOriginalMap).length > 0; |
| 50 | const parseChunk = model.includes("claude-3") |
| 51 | ? parseClaude3StreamedData |
| 52 | : model.startsWith("anthropic") |
| 53 | ? parseLegacyAnthropicStreamedData |
| 54 | : parseGPTStreamedData; |
| 55 | |
| 56 | // https://web.dev/streams/#asynchronous-iteration |
| 57 | while (!done) { |
| 58 | const { value, done: doneReading } = await reader.read(); |
| 59 | |
| 60 | done = doneReading; |
| 61 | if (done) break; |
| 62 | const contentItems = parseChunk(incompleteChunk + decoder.decode(value)); |
| 63 | |
| 64 | incompleteChunk = contentItems.incompleteChunk ?? ""; |
| 65 | |
| 66 | for (let content of contentItems.completeChunks) { |
| 67 | // Sometimes starts with a newline |
| 68 | if (first) { |
no test coverage detected