data.prompt += data.generation_prompt;
}
+ std::vector<std::string> tool_call_starts = { "<tool_call>" };
+
+ // Match complete <function=name> opener for Qwen3-Coder models that occasionally omit the
+ // starting <tool_call>. The model may hallucinate a tool name, but it is preferable over
+ // constraining on <function which may occur in valid content generation, e.g. #include <functional>
+ foreach_function(inputs.tools, [&](const json & tool) {
+ const std::string name = tool.at("function").at("name");
+ tool_call_starts.push_back("<function=" + name + ">");
+ });
+
auto parser = build_chat_peg_parser([&](common_chat_peg_builder & p) {
auto generation_prompt = p.literal(GEN_PREFIX);
auto tool_calls = p.trigger_rule("tool-call-root", p.repeat(calls, min_calls, 1));
return generation_prompt +
- (reasoning << p.content(p.until_one_of({ "<tool_call>", "<function=" })) << tool_calls);
+ (reasoning << p.content(p.until_one_of(tool_call_starts)) << tool_calls);
}
// Content only parser
});
if (data.grammar_lazy) {
- data.grammar_triggers = {
- { COMMON_GRAMMAR_TRIGGER_TYPE_WORD, "<tool_call>" },
- // Trigger on "<function" and not "<function=" because the trailing "=" is part of
- // the token with the function name e.g. "=read"
- { COMMON_GRAMMAR_TRIGGER_TYPE_WORD, "<function" },
- };
+ for (const auto & start : tool_call_starts) {
+ data.grammar_triggers.push_back({ COMMON_GRAMMAR_TRIGGER_TYPE_WORD, start });
+ }
}
}