mirror of
https://github.com/mudler/LocalAI.git
synced 2025-01-30 08:04:13 +00:00
refactor: break down json grammar parser in different files (#3004)
* refactor: break down json grammar parser in different files Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix: patch to `refactor_grammars` - propagate errors (#3006) propagate errors around Signed-off-by: Dave Lee <dave@gray101.com> --------- Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Signed-off-by: Dave Lee <dave@gray101.com> Co-authored-by: Dave <dave@gray101.com>
This commit is contained in:
parent
717cc6fe1a
commit
5eda7f578d
@ -226,9 +226,15 @@ func ChatEndpoint(cl *config.BackendConfigLoader, ml *model.ModelLoader, startup
|
|||||||
|
|
||||||
// Update input grammar
|
// Update input grammar
|
||||||
jsStruct := funcs.ToJSONStructure(config.FunctionsConfig.FunctionNameKey, config.FunctionsConfig.FunctionNameKey)
|
jsStruct := funcs.ToJSONStructure(config.FunctionsConfig.FunctionNameKey, config.FunctionsConfig.FunctionNameKey)
|
||||||
config.Grammar = jsStruct.Grammar(config.FunctionsConfig.GrammarConfig.Options()...)
|
g, err := jsStruct.Grammar(config.FunctionsConfig.GrammarConfig.Options()...)
|
||||||
|
if err == nil {
|
||||||
|
config.Grammar = g
|
||||||
|
}
|
||||||
case input.JSONFunctionGrammarObject != nil:
|
case input.JSONFunctionGrammarObject != nil:
|
||||||
config.Grammar = input.JSONFunctionGrammarObject.Grammar(config.FunctionsConfig.GrammarConfig.Options()...)
|
g, err := input.JSONFunctionGrammarObject.Grammar(config.FunctionsConfig.GrammarConfig.Options()...)
|
||||||
|
if err == nil {
|
||||||
|
config.Grammar = g
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
// Force picking one of the functions by the request
|
// Force picking one of the functions by the request
|
||||||
if config.FunctionToCall() != "" {
|
if config.FunctionToCall() != "" {
|
||||||
|
47
pkg/functions/bnf_rules.go
Normal file
47
pkg/functions/bnf_rules.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package functions
|
||||||
|
|
||||||
|
import "regexp"
|
||||||
|
|
||||||
|
var (
|
||||||
|
PRIMITIVE_RULES = map[string]string{
|
||||||
|
"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`,
|
||||||
|
// TODO: we shouldn't forbid \" and \\ or all unicode and have this branch here,
|
||||||
|
// however, if we don't have it, the grammar will be ambiguous and
|
||||||
|
// empirically results are way worse.
|
||||||
|
"freestring": `(
|
||||||
|
[^\x00] |
|
||||||
|
"\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
|
||||||
|
)* space`,
|
||||||
|
"null": `"null" space`,
|
||||||
|
}
|
||||||
|
|
||||||
|
INVALID_RULE_CHARS_RE = regexp.MustCompile(`[^a-zA-Z0-9-]+`)
|
||||||
|
GRAMMAR_LITERAL_ESCAPE_RE = regexp.MustCompile(`[\r\n"]`)
|
||||||
|
GRAMMAR_LITERAL_ESCAPES = map[string]string{
|
||||||
|
"\r": `\r`,
|
||||||
|
"\n": `\n`,
|
||||||
|
`"`: `\"`,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SPACE_RULE = `" "?`
|
||||||
|
|
||||||
|
arrayNewLines = `arr ::=
|
||||||
|
"[\n" (
|
||||||
|
realvalue
|
||||||
|
(",\n" realvalue)*
|
||||||
|
)? "]"`
|
||||||
|
|
||||||
|
array = `arr ::=
|
||||||
|
"[" (
|
||||||
|
realvalue
|
||||||
|
("," realvalue)*
|
||||||
|
)? "]"`
|
||||||
|
)
|
25
pkg/functions/function_structure.go
Normal file
25
pkg/functions/function_structure.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package functions
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
type Item struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Properties map[string]interface{} `json:"properties"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type JSONFunctionStructure struct {
|
||||||
|
OneOf []Item `json:"oneOf,omitempty"`
|
||||||
|
AnyOf []Item `json:"anyOf,omitempty"`
|
||||||
|
Defs map[string]interface{} `json:"$defs,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j JSONFunctionStructure) Grammar(options ...func(*GrammarOption)) (string, error) {
|
||||||
|
grammarOpts := &GrammarOption{}
|
||||||
|
grammarOpts.Apply(options...)
|
||||||
|
|
||||||
|
dat, err := json.Marshal(j)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return NewJSONSchemaConverter(grammarOpts.PropOrder).GrammarFromBytes(dat, options...)
|
||||||
|
}
|
@ -18,6 +18,15 @@ type Function struct {
|
|||||||
}
|
}
|
||||||
type Functions []Function
|
type Functions []Function
|
||||||
|
|
||||||
|
type FunctionName struct {
|
||||||
|
Const string `json:"const"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Argument struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Properties map[string]interface{} `json:"properties"`
|
||||||
|
}
|
||||||
|
|
||||||
type Tool struct {
|
type Tool struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Function Function `json:"function,omitempty"`
|
Function Function `json:"function,omitempty"`
|
||||||
@ -86,3 +95,11 @@ func (f Functions) Select(name string) Functions {
|
|||||||
|
|
||||||
return funcs
|
return funcs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func jsonString(v interface{}) (string, error) {
|
||||||
|
b, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
package functions
|
package functions_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/mudler/LocalAI/pkg/functions"
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
@ -11,3 +13,13 @@ func TestGrammar(t *testing.T) {
|
|||||||
RegisterFailHandler(Fail)
|
RegisterFailHandler(Fail)
|
||||||
RunSpecs(t, "Grammar test suite")
|
RunSpecs(t, "Grammar test suite")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createFunction(field1 string, field2 string, name string, properties map[string]interface{}) map[string]interface{} {
|
||||||
|
property := map[string]interface{}{}
|
||||||
|
property[field1] = FunctionName{Const: name}
|
||||||
|
property[field2] = Argument{
|
||||||
|
Type: "object",
|
||||||
|
Properties: properties,
|
||||||
|
}
|
||||||
|
return property
|
||||||
|
}
|
||||||
|
@ -5,70 +5,12 @@ package functions
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/mudler/LocalAI/pkg/utils"
|
"github.com/mudler/LocalAI/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
JSONBNF = `root ::= object
|
|
||||||
value ::= object | array | string | number | ("true" | "false" | "null") ws
|
|
||||||
|
|
||||||
object ::=
|
|
||||||
"{" ws (
|
|
||||||
string ":" ws value
|
|
||||||
("," ws string ":" ws value)*
|
|
||||||
)? "}" ws
|
|
||||||
|
|
||||||
array ::=
|
|
||||||
"[" ws (
|
|
||||||
value
|
|
||||||
("," ws value)*
|
|
||||||
)? "]" ws
|
|
||||||
|
|
||||||
string ::=
|
|
||||||
"\"" (
|
|
||||||
[^"\\] |
|
|
||||||
"\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) # escapes
|
|
||||||
)* "\"" ws
|
|
||||||
|
|
||||||
number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? ws
|
|
||||||
|
|
||||||
ws ::= ([ \t\n] ws)?`
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
SPACE_RULE = `" "?`
|
|
||||||
|
|
||||||
PRIMITIVE_RULES = map[string]string{
|
|
||||||
"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`,
|
|
||||||
// TODO: we shouldn't forbid \" and \\ or all unicode and have this branch here,
|
|
||||||
// however, if we don't have it, the grammar will be ambiguous and
|
|
||||||
// empirically results are way worse.
|
|
||||||
"freestring": `(
|
|
||||||
[^\x00] |
|
|
||||||
"\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
|
|
||||||
)* space`,
|
|
||||||
"null": `"null" space`,
|
|
||||||
}
|
|
||||||
|
|
||||||
INVALID_RULE_CHARS_RE = regexp.MustCompile(`[^a-zA-Z0-9-]+`)
|
|
||||||
GRAMMAR_LITERAL_ESCAPE_RE = regexp.MustCompile(`[\r\n"]`)
|
|
||||||
GRAMMAR_LITERAL_ESCAPES = map[string]string{
|
|
||||||
"\r": `\r`,
|
|
||||||
"\n": `\n`,
|
|
||||||
`"`: `\"`,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
type JSONSchemaConverter struct {
|
type JSONSchemaConverter struct {
|
||||||
propOrder map[string]int
|
propOrder map[string]int
|
||||||
rules map[string]string
|
rules map[string]string
|
||||||
@ -90,11 +32,15 @@ func NewJSONSchemaConverter(propOrder string) *JSONSchemaConverter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *JSONSchemaConverter) formatLiteral(literal interface{}) string {
|
func (sc *JSONSchemaConverter) formatLiteral(literal interface{}) (string, error) {
|
||||||
escaped := GRAMMAR_LITERAL_ESCAPE_RE.ReplaceAllStringFunc(jsonString(literal), func(match string) string {
|
jLiteral, err := jsonString(literal)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
escaped := GRAMMAR_LITERAL_ESCAPE_RE.ReplaceAllStringFunc(jLiteral, func(match string) string {
|
||||||
return GRAMMAR_LITERAL_ESCAPES[match]
|
return GRAMMAR_LITERAL_ESCAPES[match]
|
||||||
})
|
})
|
||||||
return fmt.Sprintf(`"%s"`, escaped)
|
return fmt.Sprintf(`"%s"`, escaped), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *JSONSchemaConverter) addRule(name, rule string) string {
|
func (sc *JSONSchemaConverter) addRule(name, rule string) string {
|
||||||
@ -114,18 +60,6 @@ func (sc *JSONSchemaConverter) addRule(name, rule string) string {
|
|||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
|
|
||||||
const arrayNewLines = `arr ::=
|
|
||||||
"[\n" (
|
|
||||||
realvalue
|
|
||||||
(",\n" realvalue)*
|
|
||||||
)? "]"`
|
|
||||||
|
|
||||||
const array = `arr ::=
|
|
||||||
"[" (
|
|
||||||
realvalue
|
|
||||||
("," realvalue)*
|
|
||||||
)? "]"`
|
|
||||||
|
|
||||||
func (sc *JSONSchemaConverter) finalizeGrammar(options ...func(*GrammarOption)) string {
|
func (sc *JSONSchemaConverter) finalizeGrammar(options ...func(*GrammarOption)) string {
|
||||||
|
|
||||||
grammarOpts := &GrammarOption{}
|
grammarOpts := &GrammarOption{}
|
||||||
@ -210,7 +144,7 @@ func (sc *JSONSchemaConverter) finalizeGrammar(options ...func(*GrammarOption))
|
|||||||
return strings.Join(lines, "\n")
|
return strings.Join(lines, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *JSONSchemaConverter) visit(schema map[string]interface{}, name string, rootSchema map[string]interface{}) string {
|
func (sc *JSONSchemaConverter) visit(schema map[string]interface{}, name string, rootSchema map[string]interface{}) (string, error) {
|
||||||
st, existType := schema["type"]
|
st, existType := schema["type"]
|
||||||
var schemaType string
|
var schemaType string
|
||||||
if existType {
|
if existType {
|
||||||
@ -229,31 +163,44 @@ func (sc *JSONSchemaConverter) visit(schema map[string]interface{}, name string,
|
|||||||
|
|
||||||
if oneOfExists {
|
if oneOfExists {
|
||||||
for i, altSchema := range oneOfSchemas {
|
for i, altSchema := range oneOfSchemas {
|
||||||
alternative := sc.visit(altSchema.(map[string]interface{}), fmt.Sprintf("%s-%d", ruleName, i), rootSchema)
|
alternative, err := sc.visit(altSchema.(map[string]interface{}), fmt.Sprintf("%s-%d", ruleName, i), rootSchema)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
alternatives = append(alternatives, alternative)
|
alternatives = append(alternatives, alternative)
|
||||||
}
|
}
|
||||||
} else if anyOfExists {
|
} else if anyOfExists {
|
||||||
for i, altSchema := range anyOfSchemas {
|
for i, altSchema := range anyOfSchemas {
|
||||||
alternative := sc.visit(altSchema.(map[string]interface{}), fmt.Sprintf("%s-%d", ruleName, i), rootSchema)
|
alternative, err := sc.visit(altSchema.(map[string]interface{}), fmt.Sprintf("%s-%d", ruleName, i), rootSchema)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
alternatives = append(alternatives, alternative)
|
alternatives = append(alternatives, alternative)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rule := strings.Join(alternatives, " | ")
|
rule := strings.Join(alternatives, " | ")
|
||||||
return sc.addRule(ruleName, rule)
|
return sc.addRule(ruleName, rule), nil
|
||||||
} else if ref, exists := schema["$ref"].(string); exists {
|
} else if ref, exists := schema["$ref"].(string); exists {
|
||||||
referencedSchema := sc.resolveReference(ref, rootSchema)
|
referencedSchema := sc.resolveReference(ref, rootSchema)
|
||||||
return sc.visit(referencedSchema, name, rootSchema)
|
return sc.visit(referencedSchema, name, rootSchema)
|
||||||
} else if constVal, exists := schema["const"]; exists {
|
} else if constVal, exists := schema["const"]; exists {
|
||||||
return sc.addRule(ruleName, sc.formatLiteral(constVal))
|
literal, err := sc.formatLiteral((constVal))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return sc.addRule(ruleName, literal), nil
|
||||||
} else if enumVals, exists := schema["enum"].([]interface{}); exists {
|
} else if enumVals, exists := schema["enum"].([]interface{}); exists {
|
||||||
var enumRules []string
|
var enumRules []string
|
||||||
for _, enumVal := range enumVals {
|
for _, enumVal := range enumVals {
|
||||||
enumRule := sc.formatLiteral(enumVal)
|
enumRule, err := sc.formatLiteral(enumVal)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
enumRules = append(enumRules, enumRule)
|
enumRules = append(enumRules, enumRule)
|
||||||
}
|
}
|
||||||
rule := strings.Join(enumRules, " | ")
|
rule := strings.Join(enumRules, " | ")
|
||||||
return sc.addRule(ruleName, rule)
|
return sc.addRule(ruleName, rule), nil
|
||||||
} else if properties, exists := schema["properties"].(map[string]interface{}); schemaType == "object" && exists {
|
} else if properties, exists := schema["properties"].(map[string]interface{}); schemaType == "object" && exists {
|
||||||
propOrder := sc.propOrder
|
propOrder := sc.propOrder
|
||||||
var propPairs []struct {
|
var propPairs []struct {
|
||||||
@ -283,21 +230,30 @@ func (sc *JSONSchemaConverter) visit(schema map[string]interface{}, name string,
|
|||||||
for i, propPair := range propPairs {
|
for i, propPair := range propPairs {
|
||||||
propName := propPair.propName
|
propName := propPair.propName
|
||||||
propSchema := propPair.propSchema
|
propSchema := propPair.propSchema
|
||||||
propRuleName := sc.visit(propSchema, fmt.Sprintf("%s-%s", ruleName, propName), rootSchema)
|
propRuleName, err := sc.visit(propSchema, fmt.Sprintf("%s-%s", ruleName, propName), rootSchema)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
lPropName, err := sc.formatLiteral(propName)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
rule.WriteString(` "," space`)
|
rule.WriteString(` "," space`)
|
||||||
}
|
}
|
||||||
|
|
||||||
rule.WriteString(fmt.Sprintf(` %s space ":" space %s`, sc.formatLiteral(propName), propRuleName))
|
rule.WriteString(fmt.Sprintf(` %s space ":" space %s`, lPropName, propRuleName))
|
||||||
}
|
}
|
||||||
|
|
||||||
rule.WriteString(` "}" space`)
|
rule.WriteString(` "}" space`)
|
||||||
return sc.addRule(ruleName, rule.String())
|
return sc.addRule(ruleName, rule.String()), nil
|
||||||
} else if items, exists := schema["items"].(map[string]interface{}); schemaType == "array" && exists {
|
} else if items, exists := schema["items"].(map[string]interface{}); schemaType == "array" && exists {
|
||||||
itemRuleName := sc.visit(items, fmt.Sprintf("%s-item", ruleName), rootSchema)
|
itemRuleName, err := sc.visit(items, fmt.Sprintf("%s-item", ruleName), rootSchema)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
rule := fmt.Sprintf(`"[" space (%s ("," space %s)*)? "]" space`, itemRuleName, itemRuleName)
|
rule := fmt.Sprintf(`"[" space (%s ("," space %s)*)? "]" space`, itemRuleName, itemRuleName)
|
||||||
return sc.addRule(ruleName, rule)
|
return sc.addRule(ruleName, rule), nil
|
||||||
} else {
|
} else {
|
||||||
primitiveRule, exists := PRIMITIVE_RULES[schemaType]
|
primitiveRule, exists := PRIMITIVE_RULES[schemaType]
|
||||||
if !exists {
|
if !exists {
|
||||||
@ -306,7 +262,7 @@ func (sc *JSONSchemaConverter) visit(schema map[string]interface{}, name string,
|
|||||||
if ruleName == "root" {
|
if ruleName == "root" {
|
||||||
schemaType = "root"
|
schemaType = "root"
|
||||||
}
|
}
|
||||||
return sc.addRule(schemaType, primitiveRule)
|
return sc.addRule(schemaType, primitiveRule), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (sc *JSONSchemaConverter) resolveReference(ref string, rootSchema map[string]interface{}) map[string]interface{} {
|
func (sc *JSONSchemaConverter) resolveReference(ref string, rootSchema map[string]interface{}) map[string]interface{} {
|
||||||
@ -332,47 +288,20 @@ func (sc *JSONSchemaConverter) resolveReference(ref string, rootSchema map[strin
|
|||||||
return def
|
return def
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *JSONSchemaConverter) Grammar(schema map[string]interface{}, options ...func(*GrammarOption)) string {
|
func (sc *JSONSchemaConverter) Grammar(schema map[string]interface{}, options ...func(*GrammarOption)) (string, error) {
|
||||||
sc.addRule("freestring", PRIMITIVE_RULES["freestring"])
|
sc.addRule("freestring", PRIMITIVE_RULES["freestring"])
|
||||||
sc.visit(schema, "", schema)
|
_, err := sc.visit(schema, "", schema)
|
||||||
return sc.finalizeGrammar(options...)
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return sc.finalizeGrammar(options...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *JSONSchemaConverter) GrammarFromBytes(b []byte, options ...func(*GrammarOption)) string {
|
func (sc *JSONSchemaConverter) GrammarFromBytes(b []byte, options ...func(*GrammarOption)) (string, error) {
|
||||||
var schema map[string]interface{}
|
var schema map[string]interface{}
|
||||||
_ = json.Unmarshal(b, &schema)
|
err := json.Unmarshal(b, &schema)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
return sc.Grammar(schema, options...)
|
return sc.Grammar(schema, options...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func jsonString(v interface{}) string {
|
|
||||||
b, _ := json.Marshal(v)
|
|
||||||
return string(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
type FunctionName struct {
|
|
||||||
Const string `json:"const"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Argument struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
Properties map[string]interface{} `json:"properties"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Item struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
Properties map[string]interface{} `json:"properties"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type JSONFunctionStructure struct {
|
|
||||||
OneOf []Item `json:"oneOf,omitempty"`
|
|
||||||
AnyOf []Item `json:"anyOf,omitempty"`
|
|
||||||
Defs map[string]interface{} `json:"$defs,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j JSONFunctionStructure) Grammar(options ...func(*GrammarOption)) string {
|
|
||||||
grammarOpts := &GrammarOption{}
|
|
||||||
grammarOpts.Apply(options...)
|
|
||||||
|
|
||||||
dat, _ := json.Marshal(j)
|
|
||||||
return NewJSONSchemaConverter(grammarOpts.PropOrder).GrammarFromBytes(dat, options...)
|
|
||||||
}
|
|
||||||
|
@ -3,22 +3,11 @@ package functions_test
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/mudler/LocalAI/pkg/functions"
|
|
||||||
. "github.com/mudler/LocalAI/pkg/functions"
|
. "github.com/mudler/LocalAI/pkg/functions"
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
||||||
func createFunction(field1 string, field2 string, name string, properties map[string]interface{}) map[string]interface{} {
|
|
||||||
property := map[string]interface{}{}
|
|
||||||
property[field1] = FunctionName{Const: name}
|
|
||||||
property[field2] = Argument{
|
|
||||||
Type: "object",
|
|
||||||
Properties: properties,
|
|
||||||
}
|
|
||||||
return property
|
|
||||||
}
|
|
||||||
|
|
||||||
var testFunctions = []Item{
|
var testFunctions = []Item{
|
||||||
{
|
{
|
||||||
Type: "object",
|
Type: "object",
|
||||||
@ -245,7 +234,8 @@ root-1-name ::= "\"search\""`
|
|||||||
var _ = Describe("JSON schema grammar tests", func() {
|
var _ = Describe("JSON schema grammar tests", func() {
|
||||||
Context("JSON", func() {
|
Context("JSON", func() {
|
||||||
It("generates a valid grammar from JSON schema", func() {
|
It("generates a valid grammar from JSON schema", func() {
|
||||||
grammar := NewJSONSchemaConverter("").GrammarFromBytes([]byte(testInput1))
|
grammar, err := NewJSONSchemaConverter("").GrammarFromBytes([]byte(testInput1))
|
||||||
|
Expect(err).To(BeNil())
|
||||||
results := strings.Split(inputResult1, "\n")
|
results := strings.Split(inputResult1, "\n")
|
||||||
for _, r := range results {
|
for _, r := range results {
|
||||||
if r != "" {
|
if r != "" {
|
||||||
@ -255,7 +245,8 @@ var _ = Describe("JSON schema grammar tests", func() {
|
|||||||
Expect(len(results)).To(Equal(len(strings.Split(grammar, "\n"))))
|
Expect(len(results)).To(Equal(len(strings.Split(grammar, "\n"))))
|
||||||
})
|
})
|
||||||
It("generates a valid grammar from JSON schema", func() {
|
It("generates a valid grammar from JSON schema", func() {
|
||||||
grammar := NewJSONSchemaConverter("").GrammarFromBytes([]byte(testInput2))
|
grammar, err := NewJSONSchemaConverter("").GrammarFromBytes([]byte(testInput2))
|
||||||
|
Expect(err).To(BeNil())
|
||||||
results := strings.Split(inputResult3, "\n")
|
results := strings.Split(inputResult3, "\n")
|
||||||
for _, r := range results {
|
for _, r := range results {
|
||||||
if r != "" {
|
if r != "" {
|
||||||
@ -269,7 +260,8 @@ var _ = Describe("JSON schema grammar tests", func() {
|
|||||||
structuredGrammar := JSONFunctionStructure{
|
structuredGrammar := JSONFunctionStructure{
|
||||||
OneOf: testFunctions}
|
OneOf: testFunctions}
|
||||||
|
|
||||||
grammar := structuredGrammar.Grammar()
|
grammar, err := structuredGrammar.Grammar()
|
||||||
|
Expect(err).To(BeNil())
|
||||||
results := strings.Split(inputResult1, "\n")
|
results := strings.Split(inputResult1, "\n")
|
||||||
for _, r := range results {
|
for _, r := range results {
|
||||||
if r != "" {
|
if r != "" {
|
||||||
@ -283,7 +275,8 @@ var _ = Describe("JSON schema grammar tests", func() {
|
|||||||
structuredGrammar := JSONFunctionStructure{
|
structuredGrammar := JSONFunctionStructure{
|
||||||
OneOf: testFunctions}
|
OneOf: testFunctions}
|
||||||
|
|
||||||
grammar := structuredGrammar.Grammar(functions.EnableMaybeArray)
|
grammar, err := structuredGrammar.Grammar(EnableMaybeArray)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
results := strings.Split(
|
results := strings.Split(
|
||||||
strings.Join([]string{
|
strings.Join([]string{
|
||||||
inputResult2,
|
inputResult2,
|
||||||
@ -301,7 +294,8 @@ var _ = Describe("JSON schema grammar tests", func() {
|
|||||||
structuredGrammar := JSONFunctionStructure{
|
structuredGrammar := JSONFunctionStructure{
|
||||||
OneOf: testFunctionsName}
|
OneOf: testFunctionsName}
|
||||||
|
|
||||||
grammar := structuredGrammar.Grammar(functions.EnableMaybeArray)
|
grammar, err := structuredGrammar.Grammar(EnableMaybeArray)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
results := strings.Split(
|
results := strings.Split(
|
||||||
strings.Join([]string{
|
strings.Join([]string{
|
||||||
inputResult4,
|
inputResult4,
|
||||||
@ -319,10 +313,11 @@ var _ = Describe("JSON schema grammar tests", func() {
|
|||||||
structuredGrammar := JSONFunctionStructure{
|
structuredGrammar := JSONFunctionStructure{
|
||||||
OneOf: testFunctionsName}
|
OneOf: testFunctionsName}
|
||||||
|
|
||||||
grammar := structuredGrammar.Grammar(
|
grammar, err := structuredGrammar.Grammar(
|
||||||
functions.SetPrefix("suffix"),
|
SetPrefix("suffix"),
|
||||||
functions.EnableMaybeArray,
|
EnableMaybeArray,
|
||||||
)
|
)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
results := strings.Split(
|
results := strings.Split(
|
||||||
strings.Join([]string{
|
strings.Join([]string{
|
||||||
rootResult(`"suffix" arr | realvalue`),
|
rootResult(`"suffix" arr | realvalue`),
|
||||||
@ -339,7 +334,8 @@ var _ = Describe("JSON schema grammar tests", func() {
|
|||||||
structuredGrammar := JSONFunctionStructure{
|
structuredGrammar := JSONFunctionStructure{
|
||||||
OneOf: testFunctionsName}
|
OneOf: testFunctionsName}
|
||||||
|
|
||||||
grammar := structuredGrammar.Grammar(functions.SetPrefix("suffix"))
|
grammar, err := structuredGrammar.Grammar(SetPrefix("suffix"))
|
||||||
|
Expect(err).To(BeNil())
|
||||||
results := strings.Split(
|
results := strings.Split(
|
||||||
strings.Join([]string{
|
strings.Join([]string{
|
||||||
rootResult(`"suffix" realvalue`),
|
rootResult(`"suffix" realvalue`),
|
||||||
@ -356,7 +352,8 @@ var _ = Describe("JSON schema grammar tests", func() {
|
|||||||
structuredGrammar := JSONFunctionStructure{
|
structuredGrammar := JSONFunctionStructure{
|
||||||
OneOf: testFunctionsName}
|
OneOf: testFunctionsName}
|
||||||
|
|
||||||
grammar := structuredGrammar.Grammar(functions.SetPrefix("suffix"), functions.EnableMaybeString)
|
grammar, err := structuredGrammar.Grammar(SetPrefix("suffix"), EnableMaybeString)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
results := strings.Split(
|
results := strings.Split(
|
||||||
strings.Join([]string{
|
strings.Join([]string{
|
||||||
rootResult(`( "suffix" realvalue | mixedstring )`),
|
rootResult(`( "suffix" realvalue | mixedstring )`),
|
||||||
@ -373,7 +370,8 @@ var _ = Describe("JSON schema grammar tests", func() {
|
|||||||
structuredGrammar := JSONFunctionStructure{
|
structuredGrammar := JSONFunctionStructure{
|
||||||
OneOf: testFunctionsName}
|
OneOf: testFunctionsName}
|
||||||
|
|
||||||
grammar := structuredGrammar.Grammar(functions.SetPrefix("suffix"), functions.EnableMaybeString, functions.EnableMaybeArray)
|
grammar, err := structuredGrammar.Grammar(SetPrefix("suffix"), EnableMaybeString, EnableMaybeArray)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
results := strings.Split(
|
results := strings.Split(
|
||||||
strings.Join([]string{
|
strings.Join([]string{
|
||||||
rootResult(`( "suffix" (arr | realvalue) | mixedstring )`),
|
rootResult(`( "suffix" (arr | realvalue) | mixedstring )`),
|
||||||
@ -392,7 +390,8 @@ var _ = Describe("JSON schema grammar tests", func() {
|
|||||||
structuredGrammar := JSONFunctionStructure{
|
structuredGrammar := JSONFunctionStructure{
|
||||||
OneOf: testFunctionsName}
|
OneOf: testFunctionsName}
|
||||||
|
|
||||||
grammar := structuredGrammar.Grammar(functions.EnableMaybeString, functions.EnableMaybeArray)
|
grammar, err := structuredGrammar.Grammar(EnableMaybeString, EnableMaybeArray)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
results := strings.Split(
|
results := strings.Split(
|
||||||
strings.Join([]string{
|
strings.Join([]string{
|
||||||
rootResult(`mixedstring | arr | realvalue`),
|
rootResult(`mixedstring | arr | realvalue`),
|
||||||
@ -410,7 +409,8 @@ var _ = Describe("JSON schema grammar tests", func() {
|
|||||||
structuredGrammar := JSONFunctionStructure{
|
structuredGrammar := JSONFunctionStructure{
|
||||||
OneOf: testFunctionsName}
|
OneOf: testFunctionsName}
|
||||||
|
|
||||||
grammar := structuredGrammar.Grammar(functions.EnableMaybeString, functions.EnableMaybeArray, functions.NoMixedFreeString)
|
grammar, err := structuredGrammar.Grammar(EnableMaybeString, EnableMaybeArray, NoMixedFreeString)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
results := strings.Split(
|
results := strings.Split(
|
||||||
strings.Join([]string{
|
strings.Join([]string{
|
||||||
rootResult(`freestring | arr | realvalue`),
|
rootResult(`freestring | arr | realvalue`),
|
||||||
@ -432,7 +432,8 @@ var _ = Describe("JSON schema grammar tests", func() {
|
|||||||
realvalue
|
realvalue
|
||||||
("," realvalue)*
|
("," realvalue)*
|
||||||
)? "]"`
|
)? "]"`
|
||||||
grammar := structuredGrammar.Grammar(functions.EnableMaybeString, functions.EnableMaybeArray, functions.DisableParallelNewLines)
|
grammar, err := structuredGrammar.Grammar(EnableMaybeString, EnableMaybeArray, DisableParallelNewLines)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
results := strings.Split(content, "\n")
|
results := strings.Split(content, "\n")
|
||||||
for _, r := range results {
|
for _, r := range results {
|
||||||
if r != "" {
|
if r != "" {
|
||||||
|
28
pkg/functions/json_mode.go
Normal file
28
pkg/functions/json_mode.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package functions
|
||||||
|
|
||||||
|
const (
|
||||||
|
JSONBNF = `root ::= object
|
||||||
|
value ::= object | array | string | number | ("true" | "false" | "null") ws
|
||||||
|
|
||||||
|
object ::=
|
||||||
|
"{" ws (
|
||||||
|
string ":" ws value
|
||||||
|
("," ws string ":" ws value)*
|
||||||
|
)? "}" ws
|
||||||
|
|
||||||
|
array ::=
|
||||||
|
"[" ws (
|
||||||
|
value
|
||||||
|
("," ws value)*
|
||||||
|
)? "]" ws
|
||||||
|
|
||||||
|
string ::=
|
||||||
|
"\"" (
|
||||||
|
[^"\\] |
|
||||||
|
"\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) # escapes
|
||||||
|
)* "\"" ws
|
||||||
|
|
||||||
|
number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? ws
|
||||||
|
|
||||||
|
ws ::= ([ \t\n] ws)?`
|
||||||
|
)
|
Loading…
x
Reference in New Issue
Block a user