mirror of
https://github.com/mudler/LocalAI.git
synced 2025-05-05 02:02:54 +00:00
feat(functions): Enable true regex replacement for the regexReplacement option (#2341)
* Adding regex capabilities to ParseFunctionCall replacement Signed-off-by: Lenaxia <github@47north.lat> * Adding tests for the regex replace in ParseFunctionCall Signed-off-by: Lenaxia <github@47north.lat> * Fixing tests and adding a test case to validate double quote replacement works Signed-off-by: Lenaxia <github@47north.lat> * Make Regex replacement stable, drop lookaheads Signed-off-by: mudler <mudler@localai.io> --------- Signed-off-by: Lenaxia <github@47north.lat> Signed-off-by: mudler <mudler@localai.io> Co-authored-by: Lenaxia <github@47north.lat> Co-authored-by: mudler <mudler@localai.io>
This commit is contained in:
parent
5f35e85e86
commit
6b6c8cdd5f
@ -3,10 +3,10 @@ package functions
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/go-skynet/LocalAI/pkg/utils"
|
"github.com/go-skynet/LocalAI/pkg/utils"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FunctionsConfig is the configuration for the tool/function call.
|
// FunctionsConfig is the configuration for the tool/function call.
|
||||||
@ -44,7 +44,7 @@ type FunctionsConfig struct {
|
|||||||
GrammarPrefix string `yaml:"grammar_prefix"`
|
GrammarPrefix string `yaml:"grammar_prefix"`
|
||||||
|
|
||||||
// ReplaceResults allow to replace strings in the results before parsing them
|
// ReplaceResults allow to replace strings in the results before parsing them
|
||||||
ReplaceResults map[string]string `yaml:"replace_results"`
|
ReplaceResults yaml.MapSlice `yaml:"replace_results"`
|
||||||
|
|
||||||
// FunctionName enable the LLM to return { "name": "function_name", "arguments": { "arg1": "value1", "arg2": "value2" } }
|
// FunctionName enable the LLM to return { "name": "function_name", "arguments": { "arg1": "value1", "arg2": "value2" } }
|
||||||
// instead of { "function": "function_name", "arguments": { "arg1": "value1", "arg2": "value2" } }.
|
// instead of { "function": "function_name", "arguments": { "arg1": "value1", "arg2": "value2" } }.
|
||||||
@ -60,9 +60,11 @@ type FuncCallResults struct {
|
|||||||
func ParseFunctionCall(llmresult string, functionConfig FunctionsConfig) []FuncCallResults {
|
func ParseFunctionCall(llmresult string, functionConfig FunctionsConfig) []FuncCallResults {
|
||||||
log.Debug().Msgf("LLM result: %s", llmresult)
|
log.Debug().Msgf("LLM result: %s", llmresult)
|
||||||
|
|
||||||
for k, v := range functionConfig.ReplaceResults {
|
for _, item := range functionConfig.ReplaceResults {
|
||||||
|
k, v := item.Key.(string), item.Value.(string)
|
||||||
log.Debug().Msgf("Replacing %s with %s", k, v)
|
log.Debug().Msgf("Replacing %s with %s", k, v)
|
||||||
llmresult = strings.ReplaceAll(llmresult, k, v)
|
re := regexp.MustCompile(k)
|
||||||
|
llmresult = re.ReplaceAllString(llmresult, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Msgf("LLM result(processed): %s", llmresult)
|
log.Debug().Msgf("LLM result(processed): %s", llmresult)
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
. "github.com/go-skynet/LocalAI/pkg/functions"
|
. "github.com/go-skynet/LocalAI/pkg/functions"
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("LocalAI function parse tests", func() {
|
var _ = Describe("LocalAI function parse tests", func() {
|
||||||
@ -50,6 +51,7 @@ var _ = Describe("LocalAI function parse tests", func() {
|
|||||||
Expect(results).To(HaveLen(0))
|
Expect(results).To(HaveLen(0))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Context("when parallel calls are enabled", func() {
|
Context("when parallel calls are enabled", func() {
|
||||||
It("should handle multiple function calls", func() {
|
It("should handle multiple function calls", func() {
|
||||||
input := `[{"function": "add", "arguments": {"x": 5, "y": 3}}, {"function": "subtract", "arguments": {"x": 10, "y": 7}}]`
|
input := `[{"function": "add", "arguments": {"x": 5, "y": 3}}, {"function": "subtract", "arguments": {"x": 10, "y": 7}}]`
|
||||||
@ -83,7 +85,7 @@ var _ = Describe("LocalAI function parse tests", func() {
|
|||||||
Expect(results[0].Arguments).To(Equal(`{"x":5,"y":3}`))
|
Expect(results[0].Arguments).To(Equal(`{"x":5,"y":3}`))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Should parse the result by matching the JSONRegexMatch", func() {
|
It("should parse the result by matching the JSONRegexMatch", func() {
|
||||||
input := `
|
input := `
|
||||||
<tool_call>
|
<tool_call>
|
||||||
{"function": "add", "arguments": {"x": 5, "y": 3}}
|
{"function": "add", "arguments": {"x": 5, "y": 3}}
|
||||||
@ -97,7 +99,7 @@ var _ = Describe("LocalAI function parse tests", func() {
|
|||||||
Expect(results[0].Arguments).To(Equal(`{"x":5,"y":3}`))
|
Expect(results[0].Arguments).To(Equal(`{"x":5,"y":3}`))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Should parse the result by matching the JSONRegexMatch", func() {
|
It("should parse the result by matching the JSONRegexMatch", func() {
|
||||||
input := `
|
input := `
|
||||||
{"function": "add", "arguments": {"x": 5, "y": 3}}
|
{"function": "add", "arguments": {"x": 5, "y": 3}}
|
||||||
</tool_call>`
|
</tool_call>`
|
||||||
@ -110,4 +112,74 @@ var _ = Describe("LocalAI function parse tests", func() {
|
|||||||
Expect(results[0].Arguments).To(Equal(`{"x":5,"y":3}`))
|
Expect(results[0].Arguments).To(Equal(`{"x":5,"y":3}`))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Context("when using ReplaceResults to clean up input", func() {
|
||||||
|
It("should replace text before and after JSON blob", func() {
|
||||||
|
input := `
|
||||||
|
Some text before the JSON
|
||||||
|
{"function": "add", "arguments": {"x": 5, "y": 3}}
|
||||||
|
Some text after the JSON
|
||||||
|
`
|
||||||
|
|
||||||
|
functionConfig.ReplaceResults = yaml.MapSlice{
|
||||||
|
{Key: `(?s)^[^{\[]*`, Value: ""},
|
||||||
|
{Key: `(?s)[^}\]]*$`, Value: ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
results := ParseFunctionCall(input, functionConfig)
|
||||||
|
Expect(results).To(HaveLen(1))
|
||||||
|
Expect(results[0].Name).To(Equal("add"))
|
||||||
|
Expect(results[0].Arguments).To(Equal(`{"x":5,"y":3}`))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should replace text before and after array JSON blob", func() {
|
||||||
|
input := `
|
||||||
|
Some text before the JSON
|
||||||
|
[{"function": "add", "arguments": {"x": 5, "y": 3}}, {"function": "subtract", "arguments": {"x": 10, "y": 7}}]
|
||||||
|
Some text after the JSON
|
||||||
|
`
|
||||||
|
functionConfig.ReplaceResults = yaml.MapSlice{
|
||||||
|
{Key: `(?s)^[^{\[]*`, Value: ""},
|
||||||
|
{Key: `(?s)[^}\]]*$`, Value: ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
results := ParseFunctionCall(input, functionConfig)
|
||||||
|
Expect(results).To(HaveLen(2))
|
||||||
|
Expect(results[0].Name).To(Equal("add"))
|
||||||
|
Expect(results[0].Arguments).To(Equal(`{"x":5,"y":3}`))
|
||||||
|
Expect(results[1].Name).To(Equal("subtract"))
|
||||||
|
Expect(results[1].Arguments).To(Equal(`{"x":10,"y":7}`))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should convert single-quoted key-value pairs to double-quoted and escape double quotes within values", func() {
|
||||||
|
input := `
|
||||||
|
Some text before the JSON
|
||||||
|
{'function': '"add"', 'arguments': {'x': 5, 'z': '"v"', 'y': 'v"value"'}}
|
||||||
|
Some text after the JSON
|
||||||
|
`
|
||||||
|
// Regex to match non-JSON characters before the JSON structure
|
||||||
|
//reBefore := regexp.MustCompile(`(?s)^.*?(?=\{|\[)`)
|
||||||
|
// Regex to match non-JSON characters after the JSON structure
|
||||||
|
//reAfter := regexp.MustCompile(`(?s)(?<=\}|\]).*$`)
|
||||||
|
|
||||||
|
functionConfig.ReplaceResults = yaml.MapSlice{
|
||||||
|
{Key: `(?s)^[^{\[]*`, Value: ""},
|
||||||
|
{Key: `(?s)[^}\]]*$`, Value: ""},
|
||||||
|
// Regex pattern to match single quotes around keys and values
|
||||||
|
// Step 1: Replace single quotes around keys and values with double quotes
|
||||||
|
{Key: `'([^']*?)'`, Value: `_DQUOTE_${1}_DQUOTE_`},
|
||||||
|
// Step 2: Replace double quotes inside values with placeholders
|
||||||
|
{Key: `\\"`, Value: `__TEMP_QUOTE__`},
|
||||||
|
{Key: `"`, Value: `\"`},
|
||||||
|
{Key: `\'`, Value: `'`},
|
||||||
|
{Key: `_DQUOTE_`, Value: `"`},
|
||||||
|
{Key: `__TEMP_QUOTE__`, Value: `"`},
|
||||||
|
}
|
||||||
|
|
||||||
|
results := ParseFunctionCall(input, functionConfig)
|
||||||
|
Expect(results).To(HaveLen(1))
|
||||||
|
Expect(results[0].Name).To(Equal("\"add\""))
|
||||||
|
Expect(results[0].Arguments).To(Equal(`{"x":5,"y":"v\"value\"","z":"\"v\""}`))
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user