Skip to content

Commit 3f7212c

Browse files
authored
feat(functions): better free string matching, allow to expect strings after JSON (#2445)
Allow now any non-character, both as suffix and prefix when mixed grammars are enabled Signed-off-by: Ettore Di Giacinto <[email protected]>
1 parent 5dc6bac commit 3f7212c

File tree

4 files changed

+43
-15
lines changed

4 files changed

+43
-15
lines changed

core/http/endpoints/openai/chat.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,10 @@ func ChatEndpoint(cl *config.BackendConfigLoader, ml *model.ModelLoader, startup
6767
return true
6868
})
6969

70+
textContentToReturn = functions.ParseTextContent(result, config.FunctionsConfig)
7071
result = functions.CleanupLLMResult(result, config.FunctionsConfig)
7172
results := functions.ParseFunctionCall(result, config.FunctionsConfig)
72-
textContentToReturn = functions.ParseTextContent(result, config.FunctionsConfig)
73+
log.Debug().Msgf("Text content to return: %s", textContentToReturn)
7374
noActionToRun := len(results) > 0 && results[0].Name == noAction || len(results) == 0
7475

7576
switch {
@@ -136,7 +137,8 @@ func ChatEndpoint(cl *config.BackendConfigLoader, ml *model.ModelLoader, startup
136137
Model: req.Model, // we have to return what the user sent here, due to OpenAI spec.
137138
Choices: []schema.Choice{{
138139
Delta: &schema.Message{
139-
Role: "assistant",
140+
Role: "assistant",
141+
Content: &textContentToReturn,
140142
ToolCalls: []schema.ToolCall{
141143
{
142144
Index: i,
@@ -477,9 +479,10 @@ func ChatEndpoint(cl *config.BackendConfigLoader, ml *model.ModelLoader, startup
477479
return
478480
}
479481

482+
textContentToReturn = functions.ParseTextContent(s, config.FunctionsConfig)
480483
s = functions.CleanupLLMResult(s, config.FunctionsConfig)
481484
results := functions.ParseFunctionCall(s, config.FunctionsConfig)
482-
textContentToReturn = functions.ParseTextContent(s, config.FunctionsConfig)
485+
log.Debug().Msgf("Text content to return: %s", textContentToReturn)
483486
noActionsToRun := len(results) > 0 && results[0].Name == noActionName || len(results) == 0
484487

485488
switch {
@@ -507,6 +510,7 @@ func ChatEndpoint(cl *config.BackendConfigLoader, ml *model.ModelLoader, startup
507510
if len(input.Tools) > 0 {
508511
// If we are using tools, we condense the function calls into
509512
// a single response choice with all the tools
513+
toolChoice.Message.Content = textContentToReturn
510514
toolChoice.Message.ToolCalls = append(toolChoice.Message.ToolCalls,
511515
schema.ToolCall{
512516
ID: id,
@@ -522,7 +526,8 @@ func ChatEndpoint(cl *config.BackendConfigLoader, ml *model.ModelLoader, startup
522526
*c = append(*c, schema.Choice{
523527
FinishReason: "function_call",
524528
Message: &schema.Message{
525-
Role: "assistant",
529+
Role: "assistant",
530+
Content: &textContentToReturn,
526531
FunctionCall: map[string]interface{}{
527532
"name": name,
528533
"arguments": args,

pkg/functions/grammar_json_schema.go

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ var (
5454
// however, if we don't have it, the grammar will be ambiguous and
5555
// empirically results are way worse.
5656
"freestring": `(
57-
[^"\\] |
57+
[^\x00] |
5858
"\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
5959
)* space`,
6060
"null": `"null" space`,
@@ -131,15 +131,15 @@ func (sc *JSONSchemaConverter) finalizeGrammar(options ...func(*GrammarOption))
131131
grammarOpts := &GrammarOption{}
132132
grammarOpts.Apply(options...)
133133

134-
suffix := grammarOpts.Suffix
134+
prefix := grammarOpts.Prefix
135135
maybeArray := grammarOpts.MaybeArray
136136
disableParallelNewLines := grammarOpts.DisableParallelNewLines
137137
maybeString := grammarOpts.MaybeString
138138
noMixedFreeString := grammarOpts.NoMixedFreeString
139139

140140
var lines []string
141141

142-
swapRoot := maybeArray || maybeString || suffix != ""
142+
swapRoot := maybeArray || maybeString || prefix != ""
143143

144144
// write down the computed rules.
145145
// if maybeArray is true, we need to add the array rule and slightly tweak the root rule
@@ -164,19 +164,19 @@ func (sc *JSONSchemaConverter) finalizeGrammar(options ...func(*GrammarOption))
164164
freestringRule = "freestring"
165165
}
166166

167-
if suffix != "" {
167+
if prefix != "" {
168168
// quote newlines in suffix
169-
suffix = utils.EscapeNewLines(suffix)
169+
prefix = utils.EscapeNewLines(prefix)
170170

171171
if maybeArray && maybeString {
172172
newRoot = "(" + newRoot + ")"
173173
}
174174

175175
if maybeString {
176176
//newRoot = "( (\"" + suffix + "\" " + newRoot + ") | freestring ) "
177-
newRoot = "( \"" + suffix + "\" " + newRoot + " | " + freestringRule + " ) "
177+
newRoot = "( \"" + prefix + "\" " + newRoot + " | " + freestringRule + " ) "
178178
} else {
179-
newRoot = "\"" + suffix + "\" " + "" + newRoot + ""
179+
newRoot = "\"" + prefix + "\" " + "" + newRoot + ""
180180
}
181181
} else if maybeString {
182182
if maybeArray {
@@ -194,9 +194,17 @@ func (sc *JSONSchemaConverter) finalizeGrammar(options ...func(*GrammarOption))
194194
}
195195

196196
if maybeArray {
197-
lines = append(lines, `mixedstring ::= freestring | freestring arr | freestring realvalue | realvalue | arr`)
197+
if grammarOpts.ExpectStringsAfterJSON {
198+
lines = append(lines, `mixedstring ::= freestring | freestring arr freestring | (freestring realvalue freestring)* | realvalue | arr`)
199+
} else {
200+
lines = append(lines, `mixedstring ::= freestring | freestring arr | freestring realvalue | realvalue | arr`)
201+
}
198202
} else {
199-
lines = append(lines, `mixedstring ::= freestring | freestring realvalue | realvalue`)
203+
if grammarOpts.ExpectStringsAfterJSON {
204+
lines = append(lines, `mixedstring ::= freestring | (freestring realvalue freestring)* | realvalue`)
205+
} else {
206+
lines = append(lines, `mixedstring ::= freestring | freestring realvalue | realvalue`)
207+
}
200208
}
201209

202210
return strings.Join(lines, "\n")

pkg/functions/options.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ package functions
22

33
type GrammarOption struct {
44
PropOrder string
5-
Suffix string
5+
Prefix string
66
MaybeArray bool
77
DisableParallelNewLines bool
88
MaybeString bool
99
NoMixedFreeString bool
10+
ExpectStringsAfterJSON bool
1011
}
1112

1213
func (o *GrammarOption) Apply(options ...func(*GrammarOption)) {
@@ -31,8 +32,13 @@ var NoMixedFreeString func(*GrammarOption) = func(o *GrammarOption) {
3132
o.NoMixedFreeString = true
3233
}
3334

35+
// ExpectStringsAfterJSON enables mixed string suffix
36+
var ExpectStringsAfterJSON func(*GrammarOption) = func(o *GrammarOption) {
37+
o.ExpectStringsAfterJSON = true
38+
}
39+
3440
func SetPrefix(suffix string) func(*GrammarOption) {
3541
return func(o *GrammarOption) {
36-
o.Suffix = suffix
42+
o.Prefix = suffix
3743
}
3844
}

pkg/functions/parse.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ type GrammarConfig struct {
2929
// Prefix is the suffix to append to the grammar when being generated
3030
// This is useful when models prepend a tag before returning JSON
3131
Prefix string `yaml:"prefix"`
32+
33+
// ExpectStringsAfterJSON enables mixed string suffix
34+
ExpectStringsAfterJSON bool `yaml:"expect_strings_after_json"`
3235
}
3336

3437
// FunctionsConfig is the configuration for the tool/function call.
@@ -98,6 +101,9 @@ func (g GrammarConfig) Options() []func(o *GrammarOption) {
98101
if g.NoMixedFreeString {
99102
opts = append(opts, NoMixedFreeString)
100103
}
104+
if g.ExpectStringsAfterJSON {
105+
opts = append(opts, ExpectStringsAfterJSON)
106+
}
101107
return opts
102108
}
103109

@@ -116,6 +122,9 @@ func CleanupLLMResult(llmresult string, functionConfig FunctionsConfig) string {
116122
}
117123

118124
func ParseTextContent(llmresult string, functionConfig FunctionsConfig) string {
125+
log.Debug().Msgf("ParseTextContent: %s", llmresult)
126+
log.Debug().Msgf("CaptureLLMResult: %s", functionConfig.CaptureLLMResult)
127+
119128
for _, r := range functionConfig.CaptureLLMResult {
120129
// We use a regex to extract the JSON object from the response
121130
var respRegex = regexp.MustCompile(r)

0 commit comments

Comments
 (0)