chat: harden peg-native tool call parsing#24329
Conversation
accept an optional leading type: function field in build_json_tools_flat_keys so openai style tool calls parse on templates whose serialization opens on the name field. return a clean error and log the unparsed fragment on a final peg parse failure instead of throwing the raw parser position and input. keep the raw arguments string in func_args_not_string when it is not valid json instead of aborting the prompt render.
|
Yeah, I'd love to see the exact repro for this. |
The problem look systematic with this model if tools are activated : I will minimize the PR to a simple error message if the parser fails. |
a final peg parse failure threw the raw parser position and input. log the unparsed fragment and raise a clearer error instead, so a model output that does not match the expected format no longer fails silently with an empty assistant turn. minimal change, no behavior change on successful parses.
|
FWIW I think the old parsing also handled this variation. I would like to see it's unconstrained output first because we may also need to adjust the grammar triggers. |
I'll redo clean real captures from my server, but this model emits this: EDIT, I confirm : It always opens with {"type": "function", ... and never the template's {"name": ..., so the lazy trigger on the name-field prefix never fires and generation stays unconstrained. The json itself is well formed, so this is a format mismatch (the type wrapper), not malformed output. Looks like the grammar triggers need to also engage on this opening. |
|
So, preferred direction: parse tolerance for the leading "type": "function", a trigger adjustment so the grammar constrains generation back to {"name": ...}, or both? Happy to implement whichever you prefer. |
|
The parsing changes are fine but we should add |
|
I did a little non-regression test by having my different family of models draw Mandelbrot in ASCII art :) The only one that was broken was this llama 3.3 now fixed |
| // Accept an optional leading "type": "function" field | ||
| auto type_field = optional(literal("\"type\"") + space() + literal(":") + space() + | ||
| literal("\"function\"") + space() + literal(",") + space()); | ||
| auto ordered_body = tool_open(literal("{")) + space() + type_field; |
There was a problem hiding this comment.
Looks good! Let's just finish up by wrapping this parsing logic in the new flag you made from the analysis.
There was a problem hiding this comment.
Done. Both the grammar trigger and the flat-keys parsing leniency are now gated on openai_wrapper_trigger, set only by the analysis workaround that matches Meta's JSON tool instruction. Other JSON_NATIVE templates no longer get the "type": "function" handling.
Thread accept_openai_wrapper from the generator to build_json_tools_flat_keys so the leading "type": "function" field is accepted only when openai_wrapper_trigger is set.
pwilkin
left a comment
There was a problem hiding this comment.
Innocent enough :) would prob be better to have this in the main analysis route instead of a workaround (the criterion could be the same), but not a big deal.
Besides, it's an older model, not very efficient for toolcalling, but it's cleaner than a blank response. |
* chat: harden peg-native tool call parsing accept an optional leading type: function field in build_json_tools_flat_keys so openai style tool calls parse on templates whose serialization opens on the name field. return a clean error and log the unparsed fragment on a final peg parse failure instead of throwing the raw parser position and input. keep the raw arguments string in func_args_not_string when it is not valid json instead of aborting the prompt render. * chat: surface peg-native parse failures a final peg parse failure threw the raw parser position and input. log the unparsed fragment and raise a clearer error instead, so a model output that does not match the expected format no longer fails silently with an empty assistant turn. minimal change, no behavior change on successful parses. * chat: handle openai style tool calls in peg-native * nits * common: scope OpenAI wrapper grammar trigger via autoparser flag * chat: gate type:function parsing leniency on the analysis flag Thread accept_openai_wrapper from the generator to build_json_tools_flat_keys so the leading "type": "function" field is accepted only when openai_wrapper_trigger is set.
* chat: harden peg-native tool call parsing accept an optional leading type: function field in build_json_tools_flat_keys so openai style tool calls parse on templates whose serialization opens on the name field. return a clean error and log the unparsed fragment on a final peg parse failure instead of throwing the raw parser position and input. keep the raw arguments string in func_args_not_string when it is not valid json instead of aborting the prompt render. * chat: surface peg-native parse failures a final peg parse failure threw the raw parser position and input. log the unparsed fragment and raise a clearer error instead, so a model output that does not match the expected format no longer fails silently with an empty assistant turn. minimal change, no behavior change on successful parses. * chat: handle openai style tool calls in peg-native * nits * common: scope OpenAI wrapper grammar trigger via autoparser flag * chat: gate type:function parsing leniency on the analysis flag Thread accept_openai_wrapper from the generator to build_json_tools_flat_keys so the leading "type": "function" field is accepted only when openai_wrapper_trigger is set.
* chat: harden peg-native tool call parsing accept an optional leading type: function field in build_json_tools_flat_keys so openai style tool calls parse on templates whose serialization opens on the name field. return a clean error and log the unparsed fragment on a final peg parse failure instead of throwing the raw parser position and input. keep the raw arguments string in func_args_not_string when it is not valid json instead of aborting the prompt render. * chat: surface peg-native parse failures a final peg parse failure threw the raw parser position and input. log the unparsed fragment and raise a clearer error instead, so a model output that does not match the expected format no longer fails silently with an empty assistant turn. minimal change, no behavior change on successful parses. * chat: handle openai style tool calls in peg-native * nits * common: scope OpenAI wrapper grammar trigger via autoparser flag * chat: gate type:function parsing leniency on the analysis flag Thread accept_openai_wrapper from the generator to build_json_tools_flat_keys so the leading "type": "function" field is accepted only when openai_wrapper_trigger is set.
* chat: harden peg-native tool call parsing accept an optional leading type: function field in build_json_tools_flat_keys so openai style tool calls parse on templates whose serialization opens on the name field. return a clean error and log the unparsed fragment on a final peg parse failure instead of throwing the raw parser position and input. keep the raw arguments string in func_args_not_string when it is not valid json instead of aborting the prompt render. * chat: surface peg-native parse failures a final peg parse failure threw the raw parser position and input. log the unparsed fragment and raise a clearer error instead, so a model output that does not match the expected format no longer fails silently with an empty assistant turn. minimal change, no behavior change on successful parses. * chat: handle openai style tool calls in peg-native * nits * common: scope OpenAI wrapper grammar trigger via autoparser flag * chat: gate type:function parsing leniency on the analysis flag Thread accept_openai_wrapper from the generator to build_json_tools_flat_keys so the leading "type": "function" field is accepted only when openai_wrapper_trigger is set. (cherry picked from commit 581e8ec)

Overview
While working we hit a silent bug on llama 3.3 dense: the assistant turn came back empty, no error shown. It only happened with tools enabled. The model emits a tool call in a variant format that the peg-native parser rejects, which blew up the whole turn. I landed a debug log first to confirm exactly what the model was emitting, then the actual fix: accept the "type": "function" variant, and fail soft on parse errors instead of tearing down the turn.
Additional information
A bug was discovered while working on this PR #23226 that allows the system to see when an error occurs; otherwise, it's silent (empty assistant turn). This PR adds the missing error if the PEG fails.
Requirements