node-llama-cpp
Version:
Run AI models locally on your machine with node.js bindings for llama.cpp. Enforce a JSON schema on the model output on the generation level
116 lines • 4.06 kB
JavaScript
import { splitText } from "lifecycle-utils";
/**
* Parses a text template into a map of parts and their prefixes and suffixes.
* This parser assumes each part occurs exactly once in the template, and that all parts must occur in the order they are defined.
* @example
* ```typescript
* const res = parseTextTemplate(
* "Hello, {{name}}! What is the {{thing}}?",
* [{
* key: "name",
* text: "{{name}}"
* }, {
* key: "thing",
* text: "{{thing}}"
* }]
* );
* expect(res).to.eql({
* name: {
* prefix: "Hello, ",
* suffix: "! What is the "
* },
* thing: {
* prefix: "What is the ",
* suffix: "?"
* }
* });
* ```
* @example
* ```typescript
* const res2 = parseTextTemplate(
* "What is the {{thing}}?",
* [{
* key: "name",
* text: "{{name}}",
* optional: true
* }, {
* key: "thing",
* text: "{{thing}}"
* }]
* );
* expect(res2).to.eql({
* thing: {
* prefix: "What is the ",
* suffix: "?"
* }
* });
* ```
*/
export function parseTextTemplate(template, parts) {
const result = {};
const templateParts = splitText(template, parts.map((part) => part.text));
let partIndex = 0;
for (let i = 0; i < templateParts.length; i++) {
const textPart = templateParts[i];
if (typeof textPart === "string")
continue;
for (; partIndex < parts.length; partIndex++) {
const part = parts[partIndex];
if (textPart.separator === part.text) {
const previousItem = i > 0
? templateParts[i - 1]
: null;
const nextItem = i < templateParts.length - 1
? templateParts[i + 1]
: null;
result[part.key] = {
prefix: typeof previousItem === "string"
? previousItem
: "",
suffix: typeof nextItem === "string"
? nextItem
: ""
};
partIndex++;
break;
}
if (part.optional != true) {
if (result[part.key] != null)
throw new Error(`Template must contain exactly one "${part.text}"`);
else if (partIndex > 0) {
const previousNonOptionalOrFoundPart = parts
.slice(0, partIndex)
.reverse()
.find((p) => (p.optional != true || result[p.key] != null));
if (previousNonOptionalOrFoundPart != null)
throw new Error(`Template must contain "${part.text}" after "${previousNonOptionalOrFoundPart.text}"`);
throw new Error(`Template must contain "${part.text}" at the beginning`);
}
else
throw new Error(`Template must contain "${part.text}" at the beginning`);
}
else
result[part.key] = undefined;
}
}
for (; partIndex < parts.length; partIndex++) {
const part = parts[partIndex];
if (part.optional == true) {
result[part.key] = undefined;
continue;
}
if (partIndex > 0) {
const previousNonOptionalOrFoundPart = parts
.slice(0, partIndex)
.reverse()
.find((p) => (p.optional != true || result[p.key] != null));
if (previousNonOptionalOrFoundPart != null)
throw new Error(`Template must contain "${part.text}" after "${previousNonOptionalOrFoundPart.text}"`);
throw new Error(`Template must contain "${part.text}" at the beginning`);
}
else
throw new Error(`Template must contain "${part.text}" at the beginning`);
}
return result;
}
//# sourceMappingURL=parseTextTemplate.js.map