146 common_chat_templates_ptr tmpls = common_chat_templates_init(model, inputs.
template_override);
154 std::vector<common_chat_msg> messages = common_chat_msgs_parse_oaicompat(common_json::parse(inputs.
messages_json));
157 common_chat_templates_inputs tmpl_inputs;
158 tmpl_inputs.messages = messages;
160 tmpl_inputs.use_jinja =
true;
164 tmpl_inputs.tools = common_chat_tools_parse_oaicompat(common_json::parse(inputs.
tools_json));
165 tmpl_inputs.tool_choice = common_chat_tool_choice_parse_oaicompat(inputs.
tool_choice);
170 tmpl_inputs.reasoning_format = common_reasoning_format_from_name(inputs.
reasoning_format);
175 tmpl_inputs.grammar = inputs.
grammar;
178 common_chat_params params = common_chat_templates_apply(tmpls.get(), tmpl_inputs);
188 if (!messages.empty() && messages[0].role ==
"system" &&
189 messages[0].content.empty() && messages[0].content_parts.empty()) {
190 bool stripped =
false;
194 common_chat_msg sys_msg;
195 sys_msg.role =
"system";
196 sys_msg.content =
"";
198 common_chat_templates_inputs sys_inputs;
199 sys_inputs.messages = {sys_msg};
200 sys_inputs.add_generation_prompt =
false;
201 sys_inputs.use_jinja =
true;
202 auto sys_params = common_chat_templates_apply(tmpls.get(), sys_inputs);
204 const auto& sys_prefix = sys_params.prompt;
205 if (!sys_prefix.empty() &&
206 params.prompt.size() >= sys_prefix.size() &&
207 params.prompt.substr(0, sys_prefix.size()) == sys_prefix) {
208 params.prompt = params.prompt.substr(sys_prefix.size());
210 LLOYAL_LOG_DEBUG(
"[chat_in::format] Stripped empty system prefix (%zu bytes)", sys_prefix.size());
212 }
catch (
const std::exception &e) {
213 LLOYAL_LOG_DEBUG(
"[chat_in::format] Primary stripping failed: %s", e.what());
221 static const std::string SENTINEL =
"\x1F__LLOYAL_SYS_STRIP__\x1F";
223 common_chat_msg sys_msg;
224 sys_msg.role =
"system";
225 sys_msg.content =
"";
226 common_chat_msg user_msg;
227 user_msg.role =
"user";
228 user_msg.content = SENTINEL;
230 common_chat_templates_inputs with_sys;
231 with_sys.messages = {sys_msg, user_msg};
232 with_sys.add_generation_prompt =
false;
233 with_sys.use_jinja =
true;
234 auto with_sys_params = common_chat_templates_apply(tmpls.get(), with_sys);
236 common_chat_templates_inputs without_sys;
237 without_sys.messages = {user_msg};
238 without_sys.add_generation_prompt =
false;
239 without_sys.use_jinja =
true;
240 auto without_sys_params = common_chat_templates_apply(tmpls.get(), without_sys);
242 const auto& with_prompt = with_sys_params.prompt;
243 const auto& without_prompt = without_sys_params.prompt;
246 if (with_prompt.size() > without_prompt.size() &&
247 with_prompt.substr(with_prompt.size() - without_prompt.size()) == without_prompt) {
248 std::string sys_prefix = with_prompt.substr(0, with_prompt.size() - without_prompt.size());
249 if (!sys_prefix.empty() &&
250 params.prompt.size() >= sys_prefix.size() &&
251 params.prompt.substr(0, sys_prefix.size()) == sys_prefix) {
252 params.prompt = params.prompt.substr(sys_prefix.size());
253 LLOYAL_LOG_DEBUG(
"[chat_in::format] Stripped empty system prefix via sentinel (%zu bytes)", sys_prefix.size());
256 LLOYAL_LOG_DEBUG(
"[chat_in::format] Sentinel subtraction failed, skipping strip");
258 }
catch (
const std::exception &e) {
259 LLOYAL_LOG_DEBUG(
"[chat_in::format] Sentinel stripping also failed: %s", e.what());
265 result.
prompt = params.prompt;
267 result.
format = params.format;
268 result.
grammar = params.grammar;
273 result.
parser = params.parser;
282 "[chat_in::format] Successfully formatted with format=%d, %zu stop tokens, grammar=%zu bytes",
283 static_cast<int>(result.
format),
289 }
catch (
const std::exception &e) {
290 LLOYAL_LOG_DEBUG(
"[chat_in::format] Template processing failed: %s", e.what());
296 common_chat_templates_ptr tmpls = common_chat_templates_init(model, inputs.
template_override);
298 static const std::string SENTINEL =
"\x1F__LLOYAL_RETRY__\x1F";
300 std::vector<common_chat_msg> messages = common_chat_msgs_parse_oaicompat(common_json::parse(inputs.
messages_json));
303 bool has_user =
false;
304 for (
const auto& m : messages) {
305 if (m.role ==
"user") { has_user =
true;
break; }
310 std::vector<common_chat_msg> augmented = messages;
311 common_chat_msg sentinel_user;
312 sentinel_user.role =
"user";
313 sentinel_user.content = SENTINEL;
314 augmented.push_back(sentinel_user);
316 common_chat_templates_inputs tmpl_inputs;
317 tmpl_inputs.messages = augmented;
318 tmpl_inputs.add_generation_prompt =
false;
319 tmpl_inputs.use_jinja =
true;
323 tmpl_inputs.tools = common_chat_tools_parse_oaicompat(common_json::parse(inputs.
tools_json));
324 tmpl_inputs.tool_choice = common_chat_tool_choice_parse_oaicompat(inputs.
tool_choice);
327 tmpl_inputs.reasoning_format = common_reasoning_format_from_name(inputs.
reasoning_format);
330 tmpl_inputs.grammar = inputs.
grammar;
332 common_chat_params params = common_chat_templates_apply(tmpls.get(), tmpl_inputs);
335 common_chat_msg user_only_msg;
336 user_only_msg.role =
"user";
337 user_only_msg.content = SENTINEL;
339 common_chat_templates_inputs user_only_inputs;
340 user_only_inputs.messages = {user_only_msg};
341 user_only_inputs.add_generation_prompt =
false;
342 user_only_inputs.use_jinja =
true;
343 auto user_only_params = common_chat_templates_apply(tmpls.get(), user_only_inputs);
345 const auto& full = params.prompt;
346 const auto& user_suffix = user_only_params.prompt;
349 if (full.size() > user_suffix.size() &&
350 full.substr(full.size() - user_suffix.size()) == user_suffix) {
351 params.prompt = full.substr(0, full.size() - user_suffix.size());
356 if (!messages.empty() && messages[0].role ==
"system" &&
357 messages[0].content.empty() && messages[0].content_parts.empty()) {
359 common_chat_msg sys_msg; sys_msg.role =
"system"; sys_msg.content =
"";
360 common_chat_msg usr_msg; usr_msg.role =
"user"; usr_msg.content = SENTINEL;
362 common_chat_templates_inputs with_sys_inputs;
363 with_sys_inputs.messages = {sys_msg, usr_msg};
364 with_sys_inputs.add_generation_prompt =
false;
365 with_sys_inputs.use_jinja =
true;
367 common_chat_templates_inputs without_sys_inputs;
368 without_sys_inputs.messages = {usr_msg};
369 without_sys_inputs.add_generation_prompt =
false;
370 without_sys_inputs.use_jinja =
true;
373 auto with_sys = common_chat_templates_apply(tmpls.get(), with_sys_inputs);
374 auto without_sys = common_chat_templates_apply(tmpls.get(), without_sys_inputs);
375 if (with_sys.prompt.size() > without_sys.prompt.size() &&
376 with_sys.prompt.substr(with_sys.prompt.size() - without_sys.prompt.size()) == without_sys.prompt) {
377 std::string sys_prefix = with_sys.prompt.substr(0, with_sys.prompt.size() - without_sys.prompt.size());
378 if (!sys_prefix.empty() &&
379 params.prompt.size() >= sys_prefix.size() &&
380 params.prompt.substr(0, sys_prefix.size()) == sys_prefix) {
381 params.prompt = params.prompt.substr(sys_prefix.size());
382 LLOYAL_LOG_DEBUG(
"[chat_in::format] Retry: stripped empty system prefix (%zu bytes)", sys_prefix.size());
390 result.
prompt = params.prompt;
392 result.
format = params.format;
393 result.
grammar = params.grammar;
398 result.
parser = params.parser;
409 "[chat_in::format] Retry with synthetic user succeeded, format=%d (%zu bytes)",
417 }
catch (
const std::exception &e2) {
425 using json = nlohmann::ordered_json;
427 std::string fallback_prompt;
428 for (
const auto &msg : messages) {
429 if (msg.contains(
"role") && msg.contains(
"content")) {
430 std::string role = msg[
"role"].get<std::string>();
432 const auto& c = msg[
"content"];
435 }
else if (c.is_string()) {
436 content = c.get<std::string>();
440 fallback_prompt += role +
": " + content +
"\n";
444 result.
prompt = fallback_prompt;
448 "[chat_in::format] Using fallback format (%zu bytes)",
449 fallback_prompt.size());
452 }
catch (
const std::exception &e) {
454 "[chat_in::format] ERROR: Failed to parse messages JSON: %s",
564 using json = nlohmann::ordered_json;
566 if (!model)
return {};
569 const std::string SENTINEL =
"\x1F__LLOYAL_SEP__\x1F";
570 const std::string SENTINEL2 =
"\x1F__LLOYAL_SEP2__\x1F";
574 common_chat_templates_ptr tmpls = common_chat_templates_init(model,
"");
580 std::vector<common_chat_msg> messages = {
581 {.role =
"user", .content =
"X"},
582 {.role =
"assistant", .content = SENTINEL},
583 {.role =
"user", .content = SENTINEL2}
586 common_chat_templates_inputs inputs;
587 inputs.messages = messages;
588 inputs.add_generation_prompt =
false;
589 inputs.use_jinja =
true;
591 auto params = common_chat_templates_apply(tmpls.get(), inputs);
592 const std::string& formatted = params.prompt;
595 size_t sep_start = formatted.rfind(SENTINEL);
596 if (sep_start == std::string::npos) {
599 sep_start += SENTINEL.length();
601 size_t sep_end = formatted.find(SENTINEL2, sep_start);
602 if (sep_end == std::string::npos) {
606 std::string between = formatted.substr(sep_start, sep_end - sep_start);
607 if (between.empty()) {
612 const auto* vocab = llama_model_get_vocab(model);
614 if (tokens.empty()) {
619 std::vector<llama_token> separator;
620 bool found_eog =
false;
622 for (
auto tok : tokens) {
624 separator.push_back(tok);
631 bool is_whitespace = !text.empty() && std::all_of(text.begin(), text.end(),
632 [](
unsigned char c) { return c ==
' ' || c ==
'\n' || c ==
'\r' || c ==
'\t'; });
634 separator.push_back(tok);
641 if (separator.empty() || !found_eog) {
647 }
catch (
const std::exception& e) {