mirror of
https://github.com/mudler/LocalAI.git
synced 2025-05-20 10:35:01 +00:00
feat(functions): simplify parsing, read functions as list (#2340)
Signed-off-by: mudler <mudler@localai.io>
This commit is contained in:
parent
9ab8f8f5e0
commit
02f1b477df
2 changed files with 56 additions and 132 deletions
|
@ -2,7 +2,6 @@ package functions
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
|
@ -68,9 +67,6 @@ func ParseFunctionCall(llmresult string, functionConfig FunctionsConfig) []FuncC
|
|||
|
||||
log.Debug().Msgf("LLM result(processed): %s", llmresult)
|
||||
|
||||
multipleResults := functionConfig.ParallelCalls
|
||||
useGrammars := !functionConfig.NoGrammar
|
||||
|
||||
functionNameKey := "function"
|
||||
if functionConfig.FunctionName {
|
||||
functionNameKey = "name"
|
||||
|
@ -78,124 +74,85 @@ func ParseFunctionCall(llmresult string, functionConfig FunctionsConfig) []FuncC
|
|||
|
||||
results := []FuncCallResults{}
|
||||
|
||||
returnResult := func(s string) (name, arguments string, e error) {
|
||||
returnResult := func(s string) (result []FuncCallResults, e error) {
|
||||
// As we have to change the result before processing, we can't stream the answer token-by-token (yet?)
|
||||
ss := map[string]interface{}{}
|
||||
// This prevent newlines to break JSON parsing for clients
|
||||
var ss []map[string]interface{}
|
||||
result = make([]FuncCallResults, 0)
|
||||
s = utils.EscapeNewLines(s)
|
||||
err := json.Unmarshal([]byte(s), &ss)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("escapedLLMResult", s).Msg("unable to unmarshal llm result")
|
||||
}
|
||||
log.Debug().Msgf("Function return: %s %+v", s, ss)
|
||||
|
||||
// The grammar defines the function name as "function", while OpenAI returns "name"
|
||||
func_name, ok := ss[functionNameKey]
|
||||
if !ok {
|
||||
return "", "", fmt.Errorf("unable to find function name in result")
|
||||
}
|
||||
// Similarly, while here arguments is a map[string]interface{}, OpenAI actually want a stringified object
|
||||
args, ok := ss["arguments"] // arguments needs to be a string, but we return an object from the grammar result (TODO: fix)
|
||||
if !ok {
|
||||
return "", "", fmt.Errorf("unable to find arguments in result")
|
||||
}
|
||||
d, _ := json.Marshal(args)
|
||||
funcName, ok := func_name.(string)
|
||||
if !ok {
|
||||
return "", "", fmt.Errorf("unable to cast function name to string")
|
||||
}
|
||||
|
||||
return funcName, string(d), nil
|
||||
}
|
||||
|
||||
// if no grammar is used, we have to extract function and arguments from the result
|
||||
if !useGrammars {
|
||||
// the response is a string that we have to parse
|
||||
result := make(map[string]string)
|
||||
|
||||
if functionConfig.ResponseRegex != "" {
|
||||
// We use named regexes here to extract the function name and arguments
|
||||
// obviously, this expects the LLM to be stable and return correctly formatted JSON
|
||||
// TODO: optimize this and pre-compile it
|
||||
var respRegex = regexp.MustCompile(functionConfig.ResponseRegex)
|
||||
match := respRegex.FindStringSubmatch(llmresult)
|
||||
for i, name := range respRegex.SubexpNames() {
|
||||
if i != 0 && name != "" && len(match) > i {
|
||||
result[name] = match[i]
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: open point about multiple results and/or mixed with chat messages
|
||||
// This is not handled as for now, we only expect one function call per response
|
||||
functionName := result[functionNameKey]
|
||||
if functionName == "" {
|
||||
return results
|
||||
}
|
||||
} else if functionConfig.JSONRegexMatch != "" {
|
||||
//re := regexp.MustCompile(`(?s)<tool_call>(.*?)</tool_call>`)
|
||||
//m:= re.FindStringSubmatch(`<tool_call>{ foo barr }</tool_call>`)
|
||||
|
||||
// We use a regex to extract the JSON object from the response
|
||||
var respRegex = regexp.MustCompile(functionConfig.JSONRegexMatch)
|
||||
match := respRegex.FindStringSubmatch(llmresult)
|
||||
if len(match) < 2 {
|
||||
return results
|
||||
}
|
||||
|
||||
funcName, args, err := returnResult(match[1])
|
||||
// If the LLM result is a single object, try unmarshaling it into a single map
|
||||
var singleObj map[string]interface{}
|
||||
err = json.Unmarshal([]byte(s), &singleObj)
|
||||
if err != nil {
|
||||
return results
|
||||
log.Warn().Err(err).Str("escapedLLMResult", s).Msg("unable to unmarshal llm result")
|
||||
} else {
|
||||
ss = []map[string]interface{}{singleObj}
|
||||
}
|
||||
|
||||
return append(results, FuncCallResults{Name: funcName, Arguments: args})
|
||||
|
||||
} else {
|
||||
|
||||
funcName, args, err := returnResult(llmresult)
|
||||
if err != nil {
|
||||
return results
|
||||
}
|
||||
|
||||
return append(results, FuncCallResults{Name: funcName, Arguments: args})
|
||||
}
|
||||
|
||||
return append(results, FuncCallResults{Name: result[functionNameKey], Arguments: result["arguments"]})
|
||||
}
|
||||
|
||||
// with grammars
|
||||
// TODO: use generics to avoid this code duplication
|
||||
if multipleResults {
|
||||
ss := []map[string]interface{}{}
|
||||
s := utils.EscapeNewLines(llmresult)
|
||||
err := json.Unmarshal([]byte(s), &ss)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("escapedLLMResult", s).Msg("multiple results: unable to unmarshal llm result")
|
||||
}
|
||||
log.Debug().Msgf("Function return: %s %+v", s, ss)
|
||||
|
||||
for _, s := range ss {
|
||||
// The grammar defines the function name as "function", while OpenAI returns "name"
|
||||
func_name, ok := s[functionNameKey]
|
||||
if !ok {
|
||||
continue
|
||||
//return result, fmt.Errorf("unable to find function name in result")
|
||||
}
|
||||
args, ok := s["arguments"]
|
||||
// Similarly, while here arguments is a map[string]interface{}, OpenAI actually want a stringified object
|
||||
args, ok := s["arguments"] // arguments needs to be a string, but we return an object from the grammar result (TODO: fix)
|
||||
if !ok {
|
||||
continue
|
||||
//return result, fmt.Errorf("unable to find arguments in result")
|
||||
}
|
||||
d, _ := json.Marshal(args)
|
||||
funcName, ok := func_name.(string)
|
||||
if !ok {
|
||||
continue
|
||||
//return result, fmt.Errorf("unable to cast function name to string")
|
||||
}
|
||||
results = append(results, FuncCallResults{Name: funcName, Arguments: string(d)})
|
||||
|
||||
result = append(result, FuncCallResults{Name: funcName, Arguments: string(d)})
|
||||
}
|
||||
} else {
|
||||
funcName, args, err := returnResult(llmresult)
|
||||
if err != nil {
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// the response is a string that we have to parse
|
||||
result := make(map[string]string)
|
||||
|
||||
if functionConfig.ResponseRegex != "" {
|
||||
// We use named regexes here to extract the function name and arguments
|
||||
// obviously, this expects the LLM to be stable and return correctly formatted JSON
|
||||
// TODO: optimize this and pre-compile it
|
||||
var respRegex = regexp.MustCompile(functionConfig.ResponseRegex)
|
||||
match := respRegex.FindStringSubmatch(llmresult)
|
||||
for i, name := range respRegex.SubexpNames() {
|
||||
if i != 0 && name != "" && len(match) > i {
|
||||
result[name] = match[i]
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: open point about multiple results and/or mixed with chat messages
|
||||
// This is not handled as for now, we only expect one function call per response
|
||||
functionName := result[functionNameKey]
|
||||
if functionName == "" {
|
||||
return results
|
||||
}
|
||||
results = append(results, FuncCallResults{Name: result[functionNameKey], Arguments: result["arguments"]})
|
||||
} else if functionConfig.JSONRegexMatch != "" {
|
||||
|
||||
// We use a regex to extract the JSON object from the response
|
||||
var respRegex = regexp.MustCompile(functionConfig.JSONRegexMatch)
|
||||
match := respRegex.FindStringSubmatch(llmresult)
|
||||
if len(match) < 2 {
|
||||
return results
|
||||
}
|
||||
|
||||
results = append(results, FuncCallResults{Name: funcName, Arguments: args})
|
||||
results, _ = returnResult(match[1])
|
||||
} else {
|
||||
results, _ = returnResult(llmresult)
|
||||
}
|
||||
|
||||
return results
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue