All files solve-json.ts

83.87% Statements 26/31
70.58% Branches 12/17
100% Functions 6/6
95.83% Lines 23/24

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152                                    2x                                                                                                                                 2x   2x   2x                     2x   1x 1x 1x                     2x 2x 2x 2x 2x         1x           4x 4x             5x 5x 3x   2x         3x 3x       1x  
/**
 * Module for solving requests using the OpenAI GPT-3 API.
 * @module solveJson
 */
 
import { SolveRequestOptions, solve } from "./solve"
import { z } from "zod";
import { getJsonSchema } from "./utils"
 
 
/**
 * Function for solving a parse error that occurs while trying to parse JSON from text.
 * @function
 * @async
 * @param {string} text - The text that contains the JSON string.
 * @param {string} error - The error message that describes the parse error.
 * @returns {Promise<SolveResponse>} - A promise that resolves to the corrected JSON string, or a JSON object with a "message" property containing the original text if the text does not contain a valid JSON string.
 */
async function solveParseError(text: string, error: string, textKey: string = 'text') { return solve(
`
###INSTRUCTIONS:
Catched error while trying to parse JSON from text. The text may contain a json string, read the error and correct it without modifing the content.
If the text do not contain a json string please return it like this "{"${textKey}":"text..."}" Make sure to scape all line breaks and bad characters!.
 
###ERROR:
${error}
 
###TEXT:
${text}
`)}
 
/**
 * Interface for a JSON solving request.
 * @interface
 * @template Output - The expected output type of the solved JSON.
 * @property {string} instructions - Instructions for solving the JSON.
 * @property {object} target - The target object to solve within the JSON.
 * @property {string} target.key - The key of the target object.
 * @property {string} target.value - The value of the target object.
 * @property {string} safeKey? - The key name of the return strinc content in case of json parse fail.
 * @property {z.ZodType<Output>} zodSchema - The Zod schema for indicating the model the output format and validating it.
 * @property {object} data - Additional data to be passed to the solving function.
 */
export interface SolveJsonRequest<Output extends object> {
    instructions: string;
    target: {
        key: string;
        value: string;
    };
    safeKey?: string;
    zodSchema: z.ZodType<Output>;
    data: {
        [key: string]: any
    }
}
 
/**
 * Type for the response of a JSON solving request.
 * @typedef {Object} SolveJsonResponse
 * @property {number} status - The status code of the response.
 * @property {Output} data - The data returned by the solving function, if successful.
 * @property {Object} data.error - The error object returned by the solving function, if unsuccessful.
 * @property {string} data.text - The original text input to the solving function.
 */
export type SolveJsonResponse<Output extends object> = {
    status: number;
    data: Output;
} | {
    status: number;
    data: { error: any; text: string; }
}
 
/**
 * Function for solving a request from a JSON object using the OpenAI GPT-3.5-turbo API. Solves the target based on the provided instructions and schema.
 * @function
 * @async
 * @template Output - The expected output type of the solved JSON.
 * @param {SolveJsonRequest<T>} json - The JSON request object containing instructions, target, schema, and data.
 * @param {SolveRequestOptions} options - Optional options for the solving process.
 * @returns {Promise<SolveJsonResponse<T>>} - A promise that resolves to the solved JSON response.
 */
export async function solveJson<Output extends object>(json: SolveJsonRequest<Output>, options?: SolveRequestOptions): Promise<SolveJsonResponse<Output>> {
 
    const verbose = options?.verbose || false
 
    const jsonSchema = getJsonSchema(json.zodSchema, 'Output')
 
    const solved = await solve([{
        role: 'system',
        content: JSON.stringify({
            instructions: `Read the data, the ${json.target.key} and use your knowledge to respond to it following this instructions and the outputSchema. ${json.instructions} \n\n Your output must be a json following the exact outputSchema. IMPORTANT: Your output will de parsed to JSON so do not output plain text!`,
            outputSchema: jsonSchema,
            [json.target.key]: json.target.value,
            data: json.data
        })
    }
    ], options)
 
    if (solved.status === 200) return fullParse(solved.data, json.zodSchema,verbose,json.safeKey)
 
    const data = JSON.parse(solved.data)
    Iif (verbose) console.log('SOLVE ERROR', data)
    return handleError(solved.status, data.error, data.text, verbose)
}
 
/**
 * Handles an error response from the OpenAI API.
 * @param {SolveApiResponse} solved - The response from the OpenAI API.
 * @param {SolveJsonRequest<Output>} json - The JSON request object.
 * @param {boolean} verbose - Whether to log verbose output or not.
 * @returns {SolveJsonResponse<Output>} - The solved JSON response.
 */
async function handleParseError<Output extends object>(text: string, error: string, z: z.ZodType<Output>, verbose: boolean, safeKey?: string): Promise<SolveJsonResponse<Output>> {
    Iif (verbose) console.log('PARSE ERROR', text, error)
    const solved = await solveParseError(text,error,safeKey)
    Iif (verbose) console.log('SOLVED?', solved)
    Eif (solved.status === 200) try { 
        return {
            status: solved.status,
            data: JSON.parse(solved.data)
        }
    } catch(error: any) {
        return handleError(1,'ERROR PARSING HANDLED PARSE ERROR', error.message, verbose)
    }
    return handleError(solved.status,'ERROR SOLVING HANDLED PARSE ERROR', solved.data, verbose)
}
 
export function handleError<Output extends object>(status: number, error: string, message: string, verbose: boolean): SolveJsonResponse<Output> {
    Iif (verbose) console.error(`STATUS: ${status}`,`ERROR: ${error}: ${message}`)
    return {
        status,
        data: { error, text: message }
    }
}
 
export function fullParse<Output extends object>(text: string, z: z.ZodType<Output>, verbose: boolean, safeKey?: string) {
    try {
        const obj = JSON.parse(text)
        return zodParse(obj,z,verbose)
    } catch (error: any) {
        return handleParseError(text,error.message,z,verbose,safeKey)
    }
}
 
function zodParse<Output extends object>(obj: Output, z: z.ZodType<Output>, verbose: boolean): SolveJsonResponse<Output> {
    const zParse = z.safeParse(obj)
    if (zParse.success) return {
        status: 200,
        data: obj
    }
    return handleError(2,'ZOD PARSE ERROR',zParse.error.message,verbose)
}