mirror of
				https://github.com/ggml-org/llama.cpp.git
				synced 2025-11-03 09:22:01 +00:00 
			
		
		
		
	* server : implement json-schema-to-grammar.mjs by follow python impl * server : add grammar support in chat.mjs * server : implement grammer param in the UI * server : generate .hpp * server : remove trailing whitespaces * server : generate .hpp * server : fix sort of prop pairs * server : optimize regex & iteration
		
			
				
	
	
		
			113 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			113 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
const SPACE_RULE = '" "?';
 | 
						|
 | 
						|
const PRIMITIVE_RULES = {
 | 
						|
  boolean: '("true" | "false") space',
 | 
						|
  number: '("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space',
 | 
						|
  integer: '("-"? ([0-9] | [1-9] [0-9]*)) space',
 | 
						|
  string: ` "\\"" (
 | 
						|
        [^"\\\\] |
 | 
						|
        "\\\\" (["\\\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
 | 
						|
      )* "\\"" space`,
 | 
						|
  null: '"null" space',
 | 
						|
};
 | 
						|
 | 
						|
const INVALID_RULE_CHARS_RE = /[^\dA-Za-z-]+/g;
 | 
						|
const GRAMMAR_LITERAL_ESCAPE_RE = /[\n\r"]/g;
 | 
						|
const GRAMMAR_LITERAL_ESCAPES = {'\r': '\\r', '\n': '\\n', '"': '\\"'};
 | 
						|
 | 
						|
export class SchemaConverter {
 | 
						|
  constructor(propOrder) {
 | 
						|
    this._propOrder = propOrder || {};
 | 
						|
    this._rules = new Map();
 | 
						|
    this._rules.set('space', SPACE_RULE);
 | 
						|
  }
 | 
						|
 | 
						|
  _formatLiteral(literal) {
 | 
						|
    const escaped = JSON.stringify(literal).replace(
 | 
						|
      GRAMMAR_LITERAL_ESCAPE_RE,
 | 
						|
      m => GRAMMAR_LITERAL_ESCAPES[m]
 | 
						|
    );
 | 
						|
    return `"${escaped}"`;
 | 
						|
  }
 | 
						|
 | 
						|
  _addRule(name, rule) {
 | 
						|
    let escName = name.replace(INVALID_RULE_CHARS_RE, '-');
 | 
						|
    let key = escName;
 | 
						|
 | 
						|
    if (this._rules.has(escName)) {
 | 
						|
      if (this._rules.get(escName) === rule) {
 | 
						|
        return key;
 | 
						|
      }
 | 
						|
 | 
						|
      let i = 0;
 | 
						|
      while (this._rules.has(`${escName}${i}`)) {
 | 
						|
        i += 1;
 | 
						|
      }
 | 
						|
      key = `${escName}${i}`;
 | 
						|
    }
 | 
						|
 | 
						|
    this._rules.set(key, rule);
 | 
						|
    return key;
 | 
						|
  }
 | 
						|
 | 
						|
  visit(schema, name) {
 | 
						|
    const schemaType = schema.type;
 | 
						|
    const ruleName = name || 'root';
 | 
						|
 | 
						|
    if (schema.oneOf || schema.anyOf) {
 | 
						|
      const rule = (schema.oneOf || schema.anyOf).map((altSchema, i) =>
 | 
						|
        this.visit(altSchema, `${name}${name ? "-" : ""}${i}`)
 | 
						|
      ).join(' | ');
 | 
						|
 | 
						|
      return this._addRule(ruleName, rule);
 | 
						|
    } else if ('const' in schema) {
 | 
						|
      return this._addRule(ruleName, this._formatLiteral(schema.const));
 | 
						|
    } else if ('enum' in schema) {
 | 
						|
      const rule = schema.enum.map(v => this._formatLiteral(v)).join(' | ');
 | 
						|
      return this._addRule(ruleName, rule);
 | 
						|
    } else if (schemaType === 'object' && 'properties' in schema) {
 | 
						|
      // TODO: `required` keyword (from python implementation)
 | 
						|
      const propOrder = this._propOrder;
 | 
						|
      const propPairs = Object.entries(schema.properties).sort((a, b) => {
 | 
						|
        // sort by position in prop_order (if specified) then by key
 | 
						|
        const orderA = typeof propOrder[a[0]] === 'number' ? propOrder[a[0]] : Infinity;
 | 
						|
        const orderB = typeof propOrder[b[0]] === 'number' ? propOrder[b[0]] : Infinity;
 | 
						|
        return orderA - orderB || a[0].localeCompare(b[0]);
 | 
						|
      });
 | 
						|
 | 
						|
      let rule = '"{" space';
 | 
						|
      propPairs.forEach(([propName, propSchema], i) => {
 | 
						|
        const propRuleName = this.visit(propSchema, `${name}${name ? "-" : ""}${propName}`);
 | 
						|
        if (i > 0) {
 | 
						|
          rule += ' "," space';
 | 
						|
        }
 | 
						|
        rule += ` ${this._formatLiteral(propName)} space ":" space ${propRuleName}`;
 | 
						|
      });
 | 
						|
      rule += ' "}" space';
 | 
						|
 | 
						|
      return this._addRule(ruleName, rule);
 | 
						|
    } else if (schemaType === 'array' && 'items' in schema) {
 | 
						|
      // TODO `prefixItems` keyword (from python implementation)
 | 
						|
      const itemRuleName = this.visit(schema.items, `${name}${name ? "-" : ""}item`);
 | 
						|
      const rule = `"[" space (${itemRuleName} ("," space ${itemRuleName})*)? "]" space`;
 | 
						|
      return this._addRule(ruleName, rule);
 | 
						|
    } else {
 | 
						|
      if (!PRIMITIVE_RULES[schemaType]) {
 | 
						|
        throw new Error(`Unrecognized schema: ${JSON.stringify(schema)}`);
 | 
						|
      }
 | 
						|
      return this._addRule(
 | 
						|
        ruleName === 'root' ? 'root' : schemaType,
 | 
						|
        PRIMITIVE_RULES[schemaType]
 | 
						|
      );
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  formatGrammar() {
 | 
						|
    let grammar = '';
 | 
						|
    this._rules.forEach((rule, name) => {
 | 
						|
      grammar += `${name} ::= ${rule}\n`;
 | 
						|
    });
 | 
						|
    return grammar;
 | 
						|
  }
 | 
						|
}
 |