|
package grammars_test |
|
|
|
import ( |
|
"strings" |
|
|
|
. "github.com/mudler/LocalAI/pkg/functions" |
|
. "github.com/mudler/LocalAI/pkg/functions/grammars" |
|
. "github.com/onsi/ginkgo/v2" |
|
. "github.com/onsi/gomega" |
|
) |
|
|
|
var testFunctions = []Item{ |
|
{ |
|
Type: "object", |
|
Properties: createFunction( |
|
"function", |
|
"arguments", |
|
"create_event", |
|
map[string]interface{}{ |
|
"title": map[string]string{"type": "string"}, |
|
"date": map[string]string{"type": "string"}, |
|
"time": map[string]string{"type": "string"}, |
|
}, |
|
), |
|
}, |
|
{ |
|
Type: "object", |
|
Properties: createFunction( |
|
"function", |
|
"arguments", |
|
"search", |
|
map[string]interface{}{ |
|
"query": map[string]string{"type": "string"}, |
|
}), |
|
}, |
|
} |
|
|
|
var testFunctionsName = []Item{ |
|
{ |
|
Type: "object", |
|
Properties: createFunction( |
|
"name", |
|
"arguments", |
|
"create_event", |
|
map[string]interface{}{ |
|
"title": map[string]string{"type": "string"}, |
|
"date": map[string]string{"type": "string"}, |
|
"time": map[string]string{"type": "string"}, |
|
}, |
|
), |
|
}, |
|
{ |
|
Type: "object", |
|
Properties: createFunction( |
|
"name", |
|
"arguments", |
|
"search", |
|
map[string]interface{}{ |
|
"query": map[string]string{"type": "string"}, |
|
}), |
|
}, |
|
} |
|
|
|
func rootResult(s string) string { |
|
return `root-0-name ::= "\"create_event\"" |
|
freestring ::= ( |
|
[^"\\] | |
|
"\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) |
|
)* space |
|
root-0 ::= "{" space "\"arguments\"" space ":" space root-0-arguments "," space "\"name\"" space ":" space root-0-name "}" space |
|
root-1-arguments ::= "{" space "\"query\"" space ":" space string "}" space |
|
realvalue ::= root-0 | root-1 |
|
root ::= ` + s + ` |
|
space ::= " "? |
|
root-0-arguments ::= "{" space "\"date\"" space ":" space string "," space "\"time\"" space ":" space string "," space "\"title\"" space ":" space string "}" space |
|
root-1 ::= "{" space "\"arguments\"" space ":" space root-1-arguments "," space "\"name\"" space ":" space root-1-name "}" space |
|
string ::= "\"" ( |
|
[^"\\] | |
|
"\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) |
|
)* "\"" space |
|
arr ::= |
|
"[\n" ( |
|
realvalue |
|
(",\n" realvalue)* |
|
)? "]" |
|
root-1-name ::= "\"search\""` |
|
} |
|
|
|
const ( |
|
testInput1 = ` |
|
{ |
|
"oneOf": [ |
|
{ |
|
"type": "object", |
|
"properties": { |
|
"function": {"const": "create_event"}, |
|
"arguments": { |
|
"type": "object", |
|
"properties": { |
|
"title": {"type": "string"}, |
|
"date": {"type": "string"}, |
|
"time": {"type": "string"} |
|
} |
|
} |
|
} |
|
}, |
|
{ |
|
"type": "object", |
|
"properties": { |
|
"function": {"const": "search"}, |
|
"arguments": { |
|
"type": "object", |
|
"properties": { |
|
"query": {"type": "string"} |
|
} |
|
} |
|
} |
|
} |
|
] |
|
}` |
|
|
|
inputResult1 = `root-0-function ::= "\"create_event\"" |
|
freestring ::= ( |
|
[^"\\] | |
|
"\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) |
|
)* space |
|
root-0 ::= "{" space "\"arguments\"" space ":" space root-0-arguments "," space "\"function\"" space ":" space root-0-function "}" space |
|
root-1-arguments ::= "{" space "\"query\"" space ":" space string "}" space |
|
root ::= root-0 | root-1 |
|
space ::= " "? |
|
root-0-arguments ::= "{" space "\"date\"" space ":" space string "," space "\"time\"" space ":" space string "," space "\"title\"" space ":" space string "}" space |
|
root-1 ::= "{" space "\"arguments\"" space ":" space root-1-arguments "," space "\"function\"" space ":" space root-1-function "}" space |
|
string ::= "\"" ( |
|
[^"\\] | |
|
"\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) |
|
)* "\"" space |
|
root-1-function ::= "\"search\""` |
|
|
|
inputResult2 = `root-0-function ::= "\"create_event\"" |
|
freestring ::= ( |
|
[^"\\] | |
|
"\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) |
|
)* space |
|
root-0 ::= "{" space "\"arguments\"" space ":" space root-0-arguments "," space "\"function\"" space ":" space root-0-function "}" space |
|
root-1-arguments ::= "{" space "\"query\"" space ":" space string "}" space |
|
realvalue ::= root-0 | root-1 |
|
root ::= arr | realvalue |
|
space ::= " "? |
|
root-0-arguments ::= "{" space "\"date\"" space ":" space string "," space "\"time\"" space ":" space string "," space "\"title\"" space ":" space string "}" space |
|
root-1 ::= "{" space "\"arguments\"" space ":" space root-1-arguments "," space "\"function\"" space ":" space root-1-function "}" space |
|
string ::= "\"" ( |
|
[^"\\] | |
|
"\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) |
|
)* "\"" space |
|
arr ::= |
|
"[\n" ( |
|
realvalue |
|
(",\n" realvalue)* |
|
)? "]" |
|
root-1-function ::= "\"search\""` |
|
|
|
testInput2 = ` |
|
{ |
|
"oneOf": [ |
|
{ |
|
"type": "object", |
|
"properties": { |
|
"name": {"const": "create_event"}, |
|
"arguments": { |
|
"type": "object", |
|
"properties": { |
|
"title": {"type": "string"}, |
|
"date": {"type": "string"}, |
|
"time": {"type": "string"} |
|
} |
|
} |
|
} |
|
}, |
|
{ |
|
"type": "object", |
|
"properties": { |
|
"name": {"const": "search"}, |
|
"arguments": { |
|
"type": "object", |
|
"properties": { |
|
"query": {"type": "string"} |
|
} |
|
} |
|
} |
|
} |
|
] |
|
}` |
|
|
|
inputResult3 = `root-0-name ::= "\"create_event\"" |
|
freestring ::= ( |
|
[^"\\] | |
|
"\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) |
|
)* space |
|
root-0 ::= "{" space "\"arguments\"" space ":" space root-0-arguments "," space "\"name\"" space ":" space root-0-name "}" space |
|
root-1-arguments ::= "{" space "\"query\"" space ":" space string "}" space |
|
root ::= root-0 | root-1 |
|
space ::= " "? |
|
root-0-arguments ::= "{" space "\"date\"" space ":" space string "," space "\"time\"" space ":" space string "," space "\"title\"" space ":" space string "}" space |
|
root-1 ::= "{" space "\"arguments\"" space ":" space root-1-arguments "," space "\"name\"" space ":" space root-1-name "}" space |
|
string ::= "\"" ( |
|
[^"\\] | |
|
"\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) |
|
)* "\"" space |
|
root-1-name ::= "\"search\""` |
|
|
|
inputResult4 = `root-0-name ::= "\"create_event\"" |
|
freestring ::= ( |
|
[^"\\] | |
|
"\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) |
|
)* space |
|
root-0 ::= "{" space "\"arguments\"" space ":" space root-0-arguments "," space "\"name\"" space ":" space root-0-name "}" space |
|
root-1-arguments ::= "{" space "\"query\"" space ":" space string "}" space |
|
realvalue ::= root-0 | root-1 |
|
root ::= arr | realvalue |
|
space ::= " "? |
|
root-0-arguments ::= "{" space "\"date\"" space ":" space string "," space "\"time\"" space ":" space string "," space "\"title\"" space ":" space string "}" space |
|
root-1 ::= "{" space "\"arguments\"" space ":" space root-1-arguments "," space "\"name\"" space ":" space root-1-name "}" space |
|
string ::= "\"" ( |
|
[^"\\] | |
|
"\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) |
|
)* "\"" space |
|
arr ::= |
|
"[\n" ( |
|
realvalue |
|
(",\n" realvalue)* |
|
)? "]" |
|
root-1-name ::= "\"search\""` |
|
) |
|
|
|
var _ = Describe("JSON schema grammar tests", func() { |
|
Context("JSON", func() { |
|
It("generates a valid grammar from JSON schema", func() { |
|
grammar, err := NewJSONSchemaConverter("").GrammarFromBytes([]byte(testInput1)) |
|
Expect(err).To(BeNil()) |
|
results := strings.Split(inputResult1, "\n") |
|
for _, r := range results { |
|
if r != "" { |
|
Expect(grammar).To(ContainSubstring(r)) |
|
} |
|
} |
|
Expect(len(results)).To(Equal(len(strings.Split(grammar, "\n")))) |
|
}) |
|
It("generates a valid grammar from JSON schema", func() { |
|
grammar, err := NewJSONSchemaConverter("").GrammarFromBytes([]byte(testInput2)) |
|
Expect(err).To(BeNil()) |
|
results := strings.Split(inputResult3, "\n") |
|
for _, r := range results { |
|
if r != "" { |
|
Expect(grammar).To(ContainSubstring(r)) |
|
} |
|
} |
|
Expect(len(results)).To(Equal(len(strings.Split(grammar, "\n")))) |
|
}) |
|
It("generates a valid grammar from JSON Objects", func() { |
|
|
|
structuredGrammar := JSONFunctionStructure{ |
|
OneOf: testFunctions} |
|
|
|
grammar, err := structuredGrammar.Grammar() |
|
Expect(err).To(BeNil()) |
|
results := strings.Split(inputResult1, "\n") |
|
for _, r := range results { |
|
if r != "" { |
|
Expect(grammar).To(ContainSubstring(r)) |
|
} |
|
} |
|
Expect(len(results)).To(Equal(len(strings.Split(grammar, "\n")))) |
|
}) |
|
|
|
It("generates a valid grammar from JSON Objects for multiple function return", func() { |
|
structuredGrammar := JSONFunctionStructure{ |
|
OneOf: testFunctions} |
|
|
|
grammar, err := structuredGrammar.Grammar(EnableMaybeArray) |
|
Expect(err).To(BeNil()) |
|
results := strings.Split( |
|
strings.Join([]string{ |
|
inputResult2, |
|
"mixedstring ::= freestring | freestring arr | freestring realvalue"}, "\n"), |
|
"\n") |
|
for _, r := range results { |
|
if r != "" { |
|
Expect(grammar).To(ContainSubstring(r)) |
|
} |
|
} |
|
Expect(len(results)).To(Equal(len(strings.Split(grammar, "\n"))), grammar) |
|
}) |
|
|
|
It("generates a valid grammar from JSON Objects for multiple function return", func() { |
|
structuredGrammar := JSONFunctionStructure{ |
|
OneOf: testFunctionsName} |
|
|
|
grammar, err := structuredGrammar.Grammar(EnableMaybeArray) |
|
Expect(err).To(BeNil()) |
|
results := strings.Split( |
|
strings.Join([]string{ |
|
inputResult4, |
|
"mixedstring ::= freestring | freestring arr | freestring realvalue"}, "\n"), |
|
"\n") |
|
for _, r := range results { |
|
if r != "" { |
|
Expect(grammar).To(ContainSubstring(r)) |
|
} |
|
} |
|
Expect(len(results)).To(Equal(len(strings.Split(grammar, "\n"))), grammar) |
|
}) |
|
|
|
It("generates a valid grammar from JSON Objects for multiple function return with a suffix and array", func() { |
|
structuredGrammar := JSONFunctionStructure{ |
|
OneOf: testFunctionsName} |
|
|
|
grammar, err := structuredGrammar.Grammar( |
|
SetPrefix("suffix"), |
|
EnableMaybeArray, |
|
) |
|
Expect(err).To(BeNil()) |
|
results := strings.Split( |
|
strings.Join([]string{ |
|
rootResult(`"suffix" arr | realvalue`), |
|
"mixedstring ::= freestring | freestring arr | freestring realvalue"}, "\n"), |
|
"\n") |
|
for _, r := range results { |
|
if r != "" { |
|
Expect(grammar).To(ContainSubstring(r)) |
|
} |
|
} |
|
Expect(len(results)).To(Equal(len(strings.Split(grammar, "\n"))), grammar) |
|
}) |
|
It("generates a valid grammar from JSON Objects with a suffix", func() { |
|
structuredGrammar := JSONFunctionStructure{ |
|
OneOf: testFunctionsName} |
|
|
|
grammar, err := structuredGrammar.Grammar(SetPrefix("suffix")) |
|
Expect(err).To(BeNil()) |
|
results := strings.Split( |
|
strings.Join([]string{ |
|
rootResult(`"suffix" realvalue`), |
|
"mixedstring ::= freestring | freestring realvalue"}, "\n"), |
|
"\n") |
|
for _, r := range results { |
|
if r != "" { |
|
Expect(grammar).To(ContainSubstring(r)) |
|
} |
|
} |
|
Expect(len(results)).To(Equal(len(strings.Split(grammar, "\n"))), grammar) |
|
}) |
|
It("generates a valid grammar from JSON Objects with a suffix and could return string", func() { |
|
structuredGrammar := JSONFunctionStructure{ |
|
OneOf: testFunctionsName} |
|
|
|
grammar, err := structuredGrammar.Grammar(SetPrefix("suffix"), EnableMaybeString) |
|
Expect(err).To(BeNil()) |
|
results := strings.Split( |
|
strings.Join([]string{ |
|
rootResult(`( "suffix" realvalue | mixedstring )`), |
|
"mixedstring ::= freestring | freestring realvalue"}, "\n"), |
|
"\n") |
|
for _, r := range results { |
|
if r != "" { |
|
Expect(grammar).To(ContainSubstring(r)) |
|
} |
|
} |
|
Expect(len(results)).To(Equal(len(strings.Split(grammar, "\n"))), grammar) |
|
}) |
|
It("generates a valid grammar from JSON Objects with a suffix that could return text or an array of tools", func() { |
|
structuredGrammar := JSONFunctionStructure{ |
|
OneOf: testFunctionsName} |
|
|
|
grammar, err := structuredGrammar.Grammar(SetPrefix("suffix"), EnableMaybeString, EnableMaybeArray) |
|
Expect(err).To(BeNil()) |
|
results := strings.Split( |
|
strings.Join([]string{ |
|
rootResult(`( "suffix" (arr | realvalue) | mixedstring )`), |
|
"mixedstring ::= freestring | freestring arr | freestring realvalue"}, "\n"), |
|
"\n") |
|
|
|
for _, r := range results { |
|
if r != "" { |
|
Expect(grammar).To(ContainSubstring(r)) |
|
} |
|
} |
|
Expect(len(results)).To(Equal(len(strings.Split(grammar, "\n"))), grammar) |
|
}) |
|
|
|
It("generates a valid grammar from JSON Objects without a suffix that could return text or an array of tools or just string", func() { |
|
structuredGrammar := JSONFunctionStructure{ |
|
OneOf: testFunctionsName} |
|
|
|
grammar, err := structuredGrammar.Grammar(EnableMaybeString, EnableMaybeArray) |
|
Expect(err).To(BeNil()) |
|
results := strings.Split( |
|
strings.Join([]string{ |
|
rootResult(`mixedstring | arr | realvalue`), |
|
"mixedstring ::= freestring | freestring arr | freestring realvalue"}, "\n"), |
|
"\n") |
|
for _, r := range results { |
|
if r != "" { |
|
Expect(grammar).To(ContainSubstring(r)) |
|
} |
|
} |
|
Expect(len(results)).To(Equal(len(strings.Split(grammar, "\n"))), grammar) |
|
}) |
|
|
|
It("generates a valid grammar from JSON Objects without a suffix that could return text or an array of tools or just string. Disables mixedstring", func() { |
|
structuredGrammar := JSONFunctionStructure{ |
|
OneOf: testFunctionsName} |
|
|
|
grammar, err := structuredGrammar.Grammar(EnableMaybeString, EnableMaybeArray, NoMixedFreeString) |
|
Expect(err).To(BeNil()) |
|
results := strings.Split( |
|
strings.Join([]string{ |
|
rootResult(`freestring | arr | realvalue`), |
|
"mixedstring ::= freestring | freestring arr | freestring realvalue"}, "\n"), |
|
"\n") |
|
for _, r := range results { |
|
if r != "" { |
|
Expect(grammar).To(ContainSubstring(r)) |
|
} |
|
} |
|
Expect(len(results)).To(Equal(len(strings.Split(grammar, "\n"))), grammar) |
|
}) |
|
|
|
It("generates parallel tools without newlines in JSON", func() { |
|
structuredGrammar := JSONFunctionStructure{ |
|
OneOf: testFunctionsName} |
|
content := `arr ::= |
|
"[" ( |
|
realvalue |
|
("," realvalue)* |
|
)? "]"` |
|
grammar, err := structuredGrammar.Grammar(EnableMaybeString, EnableMaybeArray, DisableParallelNewLines) |
|
Expect(err).To(BeNil()) |
|
results := strings.Split(content, "\n") |
|
for _, r := range results { |
|
if r != "" { |
|
Expect(grammar).To(ContainSubstring(r)) |
|
} |
|
} |
|
}) |
|
}) |
|
}) |
|
|