mirror of
https://github.com/mudler/LocalAI.git
synced 2026-09-29 01:25:03 -04:00
* fix(functions): honor function_arguments_key when building the tool grammar
All four call sites of `Functions.ToJSONStructure(name, args string)` pass
`FunctionsConfig.FunctionNameKey` as *both* arguments, so
`FunctionArgumentsKey` never reaches the grammar generator.
`ToJSONStructure` writes the two properties into the same map:
property[nameKey] = FunctionName{Const: function.Name}
property[argsKey] = Argument{...}
When `nameKey == argsKey` the second assignment overwrites the first, so a
model configured with `function_name_key` gets a grammar carrying only the
arguments object -- the `{"const": "<function name>"}` constraint is gone and
the grammar can no longer express which function was called.
With `function_name_key: function`, the generated property set collapses from
{"function": {"const": "get_weather"}, "arguments": {...}}
to
{"function": {"type": "object", "properties": {...}}}
Setting only `function_arguments_key` is equally broken in the other
direction: the grammar keeps emitting `arguments` while `ParseFunctionCall`
(pkg/functions/parse.go) looks up the configured key, so the parsed call comes
back with its arguments empty.
The default configuration is unaffected -- with both keys empty
`ToJSONStructure` falls back to `name`/`arguments` for both parameters, which
is why this went unnoticed.
The existing `ToJSONStructure()` unit test already calls the helper with two
distinct keys, so only the call sites were wrong. Extend that test with a case
that keeps both custom keys distinct and asserts the two properties survive.
Signed-off-by: Anai-Guo <antai12232931@outlook.com>
* test(functions): cover configured grammar keys
Route grammar construction through FunctionsConfig so the regression test
covers the key wiring used by every endpoint.
Assisted-by: Codex:gpt-5
* chore: empty commit to trigger workflow approval
Signed-off-by: Tai An <antai12232931@outlook.com>
---------
Signed-off-by: Anai-Guo <antai12232931@outlook.com>
Signed-off-by: Tai An <antai12232931@outlook.com>
Co-authored-by: localai-org-maint-bot <306269227+localai-org-maint-bot@users.noreply.github.com>
113 lines
3.4 KiB
Go
113 lines
3.4 KiB
Go
package functions_test
|
|
|
|
import (
|
|
. "github.com/mudler/LocalAI/pkg/functions"
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
)
|
|
|
|
var _ = Describe("LocalAI grammar functions", func() {
|
|
Describe("ToJSONStructure()", func() {
|
|
It("converts a list of functions to a JSON structure that can be parsed to a grammar", func() {
|
|
var functions Functions = []Function{
|
|
{
|
|
Name: "create_event",
|
|
Parameters: map[string]any{
|
|
"properties": map[string]any{
|
|
"event_name": map[string]any{
|
|
"type": "string",
|
|
},
|
|
"event_date": map[string]any{
|
|
"type": "string",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "search",
|
|
Parameters: map[string]any{
|
|
"properties": map[string]any{
|
|
"query": map[string]any{
|
|
"type": "string",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
js := functions.ToJSONStructure("function", "arguments")
|
|
Expect(len(js.OneOf)).To(Equal(2))
|
|
fnName := js.OneOf[0].Properties["function"].(FunctionName)
|
|
fnArgs := js.OneOf[0].Properties["arguments"].(Argument)
|
|
Expect(fnName.Const).To(Equal("create_event"))
|
|
Expect(fnArgs.Properties["event_name"].(map[string]any)["type"]).To(Equal("string"))
|
|
Expect(fnArgs.Properties["event_date"].(map[string]any)["type"]).To(Equal("string"))
|
|
|
|
fnName = js.OneOf[1].Properties["function"].(FunctionName)
|
|
fnArgs = js.OneOf[1].Properties["arguments"].(Argument)
|
|
Expect(fnName.Const).To(Equal("search"))
|
|
Expect(fnArgs.Properties["query"].(map[string]any)["type"]).To(Equal("string"))
|
|
|
|
// Test with custom keys
|
|
jsN := functions.ToJSONStructure("name", "arguments")
|
|
Expect(len(jsN.OneOf)).To(Equal(2))
|
|
|
|
fnName = jsN.OneOf[0].Properties["name"].(FunctionName)
|
|
fnArgs = jsN.OneOf[0].Properties["arguments"].(Argument)
|
|
|
|
Expect(fnName.Const).To(Equal("create_event"))
|
|
Expect(fnArgs.Properties["event_name"].(map[string]any)["type"]).To(Equal("string"))
|
|
Expect(fnArgs.Properties["event_date"].(map[string]any)["type"]).To(Equal("string"))
|
|
|
|
fnName = jsN.OneOf[1].Properties["name"].(FunctionName)
|
|
fnArgs = jsN.OneOf[1].Properties["arguments"].(Argument)
|
|
|
|
Expect(fnName.Const).To(Equal("search"))
|
|
Expect(fnArgs.Properties["query"].(map[string]any)["type"]).To(Equal("string"))
|
|
})
|
|
|
|
It("keeps the name and the arguments in separate properties when both keys are customized", func() {
|
|
var functions Functions = []Function{
|
|
{
|
|
Name: "get_weather",
|
|
Parameters: map[string]any{
|
|
"properties": map[string]any{
|
|
"city": map[string]any{
|
|
"type": "string",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
config := FunctionsConfig{
|
|
FunctionNameKey: "function",
|
|
FunctionArgumentsKey: "parameters",
|
|
}
|
|
js := config.ToJSONStructure(functions)
|
|
Expect(js.OneOf[0].Properties).To(HaveLen(2))
|
|
|
|
fnName := js.OneOf[0].Properties["function"].(FunctionName)
|
|
fnArgs := js.OneOf[0].Properties["parameters"].(Argument)
|
|
Expect(fnName.Const).To(Equal("get_weather"))
|
|
Expect(fnArgs.Properties["city"].(map[string]any)["type"]).To(Equal("string"))
|
|
})
|
|
})
|
|
Context("Select()", func() {
|
|
It("selects one of the functions and returns a list containing only the selected one", func() {
|
|
var functions Functions = []Function{
|
|
{
|
|
Name: "create_event",
|
|
},
|
|
{
|
|
Name: "search",
|
|
},
|
|
}
|
|
|
|
functions = functions.Select("create_event")
|
|
Expect(len(functions)).To(Equal(1))
|
|
Expect(functions[0].Name).To(Equal("create_event"))
|
|
})
|
|
})
|
|
})
|