@anuran-roy/langtache
Version:
Mustache-driven chat prompt helpers
214 lines (206 loc) • 7.11 kB
JavaScript
// src/utils/chat.ts
import Mustache from "mustache";
import { zodToJsonSchema } from "zod-to-json-schema";
// src/utils/parseUntilJson.ts
import { ALL, parseJSON } from "partial-json";
function isObjectorArray(value) {
return Array.isArray(value) || typeof value === "object" && value !== null;
}
function parseUntilJson(jsonstr) {
let textToParse = jsonstr.trim();
let parsedJson = null;
if (textToParse.startsWith('"') && textToParse.endsWith('"')) {
const potentialJsonContent = textToParse.slice(1, -1);
try {
parsedJson = JSON.parse(potentialJsonContent);
if (isObjectorArray(parsedJson)) {
console.info("Parsed successfully after removing outer quotes.");
return parsedJson;
} else {
console.warn(
"Parsed after removing outer quotes, but result is not an object:",
parsedJson
);
parsedJson = null;
textToParse = potentialJsonContent;
}
} catch (e) {
console.info(
"Failed to parse content within outer quotes, proceeding to cleanup."
);
textToParse = potentialJsonContent;
}
}
if (parsedJson === null) {
try {
parsedJson = JSON.parse(textToParse);
if (isObjectorArray(parsedJson)) {
console.info("Parsed successfully using standard JSON.parse.");
return parsedJson;
} else {
console.warn(
"Standard JSON.parse succeeded, but result is not an object:",
parsedJson
);
parsedJson = null;
}
} catch (error) {
console.info(
"Standard JSON.parse failed, proceeding to cleanup and partial parsing."
);
}
}
if (parsedJson === null) {
if (textToParse.startsWith("```json")) {
textToParse = textToParse.substring(7);
}
if (textToParse.endsWith("```")) {
textToParse = textToParse.slice(0, -3);
}
textToParse = textToParse.trim();
const curlIndex = textToParse.indexOf("{");
const sqIndex = textToParse.indexOf("[");
let startIndex = -1;
if (curlIndex !== -1 && sqIndex !== -1) {
startIndex = Math.min(curlIndex, sqIndex);
} else if (curlIndex !== -1) {
startIndex = curlIndex;
} else if (sqIndex !== -1) {
startIndex = sqIndex;
}
if (startIndex > 0) {
console.info(
`Trimming content before first '{' or '[' at index ${startIndex}`
);
textToParse = textToParse.substring(startIndex);
} else if (startIndex === -1 && !textToParse.startsWith("{") && !textToParse.startsWith("[")) {
console.error(
"No JSON object or array start found in the string after cleanup."
);
return {};
}
try {
parsedJson = JSON.parse(textToParse);
if (isObjectorArray(parsedJson)) {
console.info("Parsed successfully using standard JSON.parse.");
return parsedJson;
} else {
console.warn(
"Standard JSON.parse succeeded, but result is not an object:",
parsedJson
);
parsedJson = null;
}
} catch (error) {
console.info(
"Standard JSON.parse failed, proceeding to cleanup and partial parsing."
);
}
try {
parsedJson = parseJSON(textToParse, ALL);
if (isObjectorArray(parsedJson)) {
console.info("Successfully parsed JSON using partial JSON parser.");
return parsedJson;
} else {
console.error(
"Partial JSON parser did not return an object:",
parsedJson
);
console.info("Final string attempted by partial parser:", textToParse);
return {};
}
} catch (error) {
console.error("Error parsing the JSON even with partial parser:", error);
console.info("Final string attempted by partial parser:", textToParse);
return {};
}
}
console.error("Parsing failed through all stages unexpectedly.");
return {};
}
// src/utils/chat.ts
var ChatPromptTemplate = class _ChatPromptTemplate {
template = "";
llm = null;
variables = null;
invokeFn = () => {
return "";
};
constructor(params) {
this.template = params.template;
this.variables = params.inputVariables;
}
static fromTemplate(template) {
const variableRegex = /{{\s*([a-zA-Z0-9_]+)\s*}}/g;
const variables = [];
let match = variableRegex.exec(template);
while (match !== null) {
variables.push(match[1] ?? "");
match = variableRegex.exec(template);
}
const instance = new _ChatPromptTemplate({
template,
inputVariables: variables
});
instance.template = template;
return instance;
}
pipe(arg) {
if (typeof arg === "function") {
this.invokeFn = arg;
return this;
} else {
this.llm = arg;
return this;
}
}
format(params) {
const paramsInVariables = Object.fromEntries(
Object.keys(params).filter((key) => (this.variables ?? []).includes(key)).map((key) => [key, params[key]])
);
const finalTemplate = Mustache.render(this.template, paramsInVariables);
return finalTemplate;
}
async invoke(model, params) {
let finalTemplate = this.format(params);
if (!!this.invokeFn) {
finalTemplate = this.invokeFn(finalTemplate);
}
if (!this.llm) {
throw new Error("Cannot invoke without initializing with an LLM Object.");
}
return this.llm.chat.completions.create({
model,
messages: [{ content: finalTemplate, role: "user", name: "user" }]
});
}
};
function getStructuredOutput(schema) {
const templateStr = `You must format your output as a JSON value that adheres to a given "JSON Schema" instance.
"JSON Schema" is a declarative language that allows you to annotate and validate JSON documents.
For example, the example "JSON Schema" instance {{"properties": {{"foo": {{"description": "a list of test words", "type": "array", "items": {{"type": "string"}}}}}}, "required": ["foo"]}}}}
would match an object with one required property, "foo". The "type" property specifies "foo" must be an "array", and the "description" property semantically describes it as "a list of test words". The items within "foo" must be strings.
Thus, the object {{"foo": ["bar", "baz"]}} is a well-formatted instance of this example "JSON Schema". The object {{"properties": {{"foo": ["bar", "baz"]}}}} is not well-formatted.
Your output will be parsed and type-checked according to the provided schema instance, so make sure all fields in your output match the schema exactly and there are no trailing commas!
Here is the JSON Schema instance your output must adhere to. Include the enclosing markdown codeblock:
\`\`\`json
${JSON.stringify(zodToJsonSchema(schema))}
\`\`\`
The given data is:
\`\`\`
{{data}}
\`\`\`
`;
return (data) => Mustache.render(templateStr, { data: JSON.stringify(data) });
}
function getValidatedOutput(schema, data) {
const parsedData = parseUntilJson(data);
return schema.safeParse(parsedData);
}
export {
ChatPromptTemplate,
getStructuredOutput,
getValidatedOutput,
isObjectorArray,
parseUntilJson
};