import { stringify } from "yaml"; import { encode } from "gpt-3-encoder"; /** * Utilities for working with Large Language Model responses. */ export class Response { /** * Fuzzy JSON parser. * @param text text to parse. * @returns The parsed object or undefined if the object could not be parsed. */ public static parseJSON(text: string): TObject | undefined { const startBrace = text.indexOf('{'); if (startBrace >= 0) { // Find substring const objText = text.substring(startBrace); const nesting = ['}'] let cleaned = '{'; let inString = false; for (let i = 1; i < objText.length && nesting.length > 0; i++) { let ch = objText[i]; if (inString) { cleaned += ch; if (ch == '\\') { // Skip escape char i++; if (i < objText.length) { cleaned += objText[i]; } else { // Malformed return undefined; } } else if (ch == '"') { inString = false; } } else { switch (ch) { case '"': inString = true; break; case '{': nesting.push('}'); break; case '[': nesting.push(']'); break; case '}': const closeObject = nesting.pop(); if (closeObject != '}') { // Malformed return undefined; } break; case ']': const closeArray = nesting.pop(); if (closeArray != ']') { // Malformed return undefined; } break; case '<': // The model sometimes fails to wrap with double quotes ch = `"<`; break; case '>': // Catch the tail end of a template param ch = `>"`; break; } cleaned += ch; } } // Is the object incomplete? if (nesting.length > 0) { // Lets close it and try to parse anyway cleaned += nesting.reverse().join(''); } // Parse cleaned up object try { const obj = JSON.parse(cleaned); return Object.keys(obj).length > 0 ? obj : undefined; } catch (err) { return undefined; } } else { return undefined; } } /** * Removes any empty fields from an object. * * @remarks * Empty fields include `null`, `undefined`, `[]`, `{}`, and `""`. * @param obj The object to clean. * @returns A cleaned object. */ public static removeEmptyValuesFromObject(obj: Record): Record { const result: Record = {}; for (const key in obj) { const value = obj[key]; const type = typeof value; switch (type) { case 'string': if (value.length == 0) { continue; } break; case 'object': if (value == null) { continue; } else if (Array.isArray(value)) { if (value.length == 0) { continue; } } else if (typeof (value as Date).toISOString !== 'function') { const cleaned = this.removeEmptyValuesFromObject(value); if (Object.keys(cleaned).length == 0) { continue; } } break; case 'undefined': continue; } result[key] = value; } return result; } }