This commit is contained in:
Olivier Chafik 2025-01-22 16:16:27 +00:00 committed by GitHub
parent 96f4053934
commit c64d2becb1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 54 additions and 11 deletions

View File

@ -25,6 +25,7 @@ class chat_template {
// Meta-Llama-3.1-8B-Instruct's template expects arguments to be an object. // Meta-Llama-3.1-8B-Instruct's template expects arguments to be an object.
// Most other templates (and OpenAI's API) expect the arguments object to be stringified. // Most other templates (and OpenAI's API) expect the arguments object to be stringified.
bool requires_object_arguments_ = false; bool requires_object_arguments_ = false;
bool requires_typed_content_ = false;
bool supports_system_role_ = true; bool supports_system_role_ = true;
bool supports_parallel_tool_calls_ = false; bool supports_parallel_tool_calls_ = false;
std::string source_; std::string source_;
@ -32,14 +33,14 @@ class chat_template {
std::string eos_token_; std::string eos_token_;
std::shared_ptr<minja::TemplateNode> template_root_; std::shared_ptr<minja::TemplateNode> template_root_;
std::string try_render( std::string try_raw_render(
const nlohmann::ordered_json & messages, const nlohmann::ordered_json & messages,
const nlohmann::ordered_json & tools, const nlohmann::ordered_json & tools,
bool add_generation_prompt, bool add_generation_prompt,
const nlohmann::ordered_json & extra_context = nlohmann::ordered_json()) const const nlohmann::ordered_json & extra_context = nlohmann::ordered_json()) const
{ {
try { try {
auto prompt = apply(messages, tools, add_generation_prompt, extra_context); auto prompt = apply(messages, tools, add_generation_prompt, extra_context, /* adjust_inputs= */ false);
// fprintf(stderr, "Prompt: %s\n", prompt.c_str()); // fprintf(stderr, "Prompt: %s\n", prompt.c_str());
return prompt; return prompt;
} catch (const std::exception & e) { } catch (const std::exception & e) {
@ -60,7 +61,7 @@ class chat_template {
supports_tools_ = source.find("tools") != std::string::npos; supports_tools_ = source.find("tools") != std::string::npos;
auto renders_string_arguments = auto renders_string_arguments =
try_render({ try_raw_render({
{ {
{"role", "user"}, {"role", "user"},
{"content", "Hey"} {"content", "Hey"}
@ -81,7 +82,7 @@ class chat_template {
}, {}, false).find("{\"code\": \"print") != std::string::npos; }, {}, false).find("{\"code\": \"print") != std::string::npos;
if (!renders_string_arguments) { if (!renders_string_arguments) {
auto renders_object_arguments = auto renders_object_arguments =
try_render({ try_raw_render({
{ {
{"role", "user"}, {"role", "user"},
{"content", "Hey"} {"content", "Hey"}
@ -106,10 +107,13 @@ class chat_template {
} }
supports_parallel_tool_calls_ = source.find("tool_call_id") != std::string::npos; supports_parallel_tool_calls_ = source.find("tool_call_id") != std::string::npos;
supports_system_role_ = try_render({ supports_system_role_ = try_raw_render({
{{"role", "system"}, {"content", "<System Needle>"}}, {{"role", "system"}, {"content", "<System Needle>"}},
{{"role", "user"}, {"content", "Hey"}} {{"role", "user"}, {"content", "Hey"}}
}, {}, false).find("<System Needle>") != std::string::npos; }, {}, false).find("<System Needle>") != std::string::npos;
requires_typed_content_ = try_raw_render({{{"role", "user"}, {"content", "Hey"}}}, {}, false).find("Hey") == std::string::npos
&& try_raw_render({{{"role", "user"}, {"content", {{{"type", "text"}, {"text", "Hey"}}}}}}, {}, false).find("Hey") != std::string::npos;
} }
const std::string & source() const { return source_; } const std::string & source() const { return source_; }
@ -122,19 +126,34 @@ class chat_template {
const nlohmann::ordered_json & messages, const nlohmann::ordered_json & messages,
const nlohmann::ordered_json & tools, const nlohmann::ordered_json & tools,
bool add_generation_prompt, bool add_generation_prompt,
const nlohmann::ordered_json & extra_context = nlohmann::ordered_json()) const const nlohmann::ordered_json & extra_context = nlohmann::ordered_json(),
bool adjust_inputs = true) const
{ {
json actual_messages; json actual_messages;
// First, "fix" messages so they have a chance to be rendered correctly by the template // First, "fix" messages so they have a chance to be rendered correctly by the template
if (requires_object_arguments_ || !supports_system_role_ || !supports_tools_) { if (adjust_inputs && (requires_object_arguments_ || !supports_system_role_ || !supports_tools_ || requires_typed_content_)) {
actual_messages = json::array(); actual_messages = json::array();
auto add_message = [&](const json & msg) {
if (requires_typed_content_ && msg.contains("content") && !msg.at("content").is_null() && msg.at("content").is_string()) {
actual_messages.push_back({
{"role", msg.at("role")},
{"content", {{
{"type", "text"},
{"text", msg.at("content")},
}}},
});
} else {
actual_messages.push_back(msg);
}
};
std::string pending_system; std::string pending_system;
auto flush_sys = [&]() { auto flush_sys = [&]() {
if (!pending_system.empty()) { if (!pending_system.empty()) {
actual_messages.push_back({ add_message({
{"role", "user"}, {"role", "user"},
{"content", pending_system}, {"content", pending_system},
}); });
@ -217,7 +236,7 @@ class chat_template {
} }
} }
} }
actual_messages.push_back(message); add_message(message);
} }
flush_sys(); flush_sys();
} else { } else {

View File

@ -693,7 +693,7 @@ enum SpaceHandling { Keep, Strip, StripSpaces, StripNewline };
class TemplateToken { class TemplateToken {
public: public:
enum class Type { Text, Expression, If, Else, Elif, EndIf, For, EndFor, Set, EndSet, Comment, Macro, EndMacro, Filter, EndFilter }; enum class Type { Text, Expression, If, Else, Elif, EndIf, For, EndFor, Generation, EndGeneration, Set, EndSet, Comment, Macro, EndMacro, Filter, EndFilter };
static std::string typeToString(Type t) { static std::string typeToString(Type t) {
switch (t) { switch (t) {
@ -712,6 +712,8 @@ public:
case Type::EndMacro: return "endmacro"; case Type::EndMacro: return "endmacro";
case Type::Filter: return "filter"; case Type::Filter: return "filter";
case Type::EndFilter: return "endfilter"; case Type::EndFilter: return "endfilter";
case Type::Generation: return "generation";
case Type::EndGeneration: return "endgeneration";
} }
return "Unknown"; return "Unknown";
} }
@ -788,6 +790,14 @@ struct EndForTemplateToken : public TemplateToken {
EndForTemplateToken(const Location & location, SpaceHandling pre, SpaceHandling post) : TemplateToken(Type::EndFor, location, pre, post) {} EndForTemplateToken(const Location & location, SpaceHandling pre, SpaceHandling post) : TemplateToken(Type::EndFor, location, pre, post) {}
}; };
struct GenerationTemplateToken : public TemplateToken {
GenerationTemplateToken(const Location & location, SpaceHandling pre, SpaceHandling post) : TemplateToken(Type::Generation, location, pre, post) {}
};
struct EndGenerationTemplateToken : public TemplateToken {
EndGenerationTemplateToken(const Location & location, SpaceHandling pre, SpaceHandling post) : TemplateToken(Type::EndGeneration, location, pre, post) {}
};
struct SetTemplateToken : public TemplateToken { struct SetTemplateToken : public TemplateToken {
std::string ns; std::string ns;
std::vector<std::string> var_names; std::vector<std::string> var_names;
@ -2149,7 +2159,7 @@ private:
static std::regex comment_tok(R"(\{#([-~]?)(.*?)([-~]?)#\})"); static std::regex comment_tok(R"(\{#([-~]?)(.*?)([-~]?)#\})");
static std::regex expr_open_regex(R"(\{\{([-~])?)"); static std::regex expr_open_regex(R"(\{\{([-~])?)");
static std::regex block_open_regex(R"(^\{%([-~])?[\s\n\r]*)"); static std::regex block_open_regex(R"(^\{%([-~])?[\s\n\r]*)");
static std::regex block_keyword_tok(R"((if|else|elif|endif|for|endfor|set|endset|block|endblock|macro|endmacro|filter|endfilter)\b)"); static std::regex block_keyword_tok(R"((if|else|elif|endif|for|endfor|generation|endgeneration|set|endset|block|endblock|macro|endmacro|filter|endfilter)\b)");
static std::regex non_text_open_regex(R"(\{\{|\{%|\{#)"); static std::regex non_text_open_regex(R"(\{\{|\{%|\{#)");
static std::regex expr_close_regex(R"([\s\n\r]*([-~])?\}\})"); static std::regex expr_close_regex(R"([\s\n\r]*([-~])?\}\})");
static std::regex block_close_regex(R"([\s\n\r]*([-~])?%\})"); static std::regex block_close_regex(R"([\s\n\r]*([-~])?%\})");
@ -2229,6 +2239,12 @@ private:
} else if (keyword == "endfor") { } else if (keyword == "endfor") {
auto post_space = parseBlockClose(); auto post_space = parseBlockClose();
tokens.push_back(std::make_unique<EndForTemplateToken>(location, pre_space, post_space)); tokens.push_back(std::make_unique<EndForTemplateToken>(location, pre_space, post_space));
} else if (keyword == "generation") {
auto post_space = parseBlockClose();
tokens.push_back(std::make_unique<GenerationTemplateToken>(location, pre_space, post_space));
} else if (keyword == "endgeneration") {
auto post_space = parseBlockClose();
tokens.push_back(std::make_unique<EndGenerationTemplateToken>(location, pre_space, post_space));
} else if (keyword == "set") { } else if (keyword == "set") {
static std::regex namespaced_var_regex(R"((\w+)[\s\n\r]*\.[\s\n\r]*(\w+))"); static std::regex namespaced_var_regex(R"((\w+)[\s\n\r]*\.[\s\n\r]*(\w+))");
@ -2330,6 +2346,13 @@ private:
throw unterminated(**start); throw unterminated(**start);
} }
children.emplace_back(std::make_shared<ForNode>(token->location, std::move(for_token->var_names), std::move(for_token->iterable), std::move(for_token->condition), std::move(body), for_token->recursive, std::move(else_body))); children.emplace_back(std::make_shared<ForNode>(token->location, std::move(for_token->var_names), std::move(for_token->iterable), std::move(for_token->condition), std::move(body), for_token->recursive, std::move(else_body)));
} else if (dynamic_cast<GenerationTemplateToken*>(token.get())) {
auto body = parseTemplate(begin, it, end);
if (it == end || (*(it++))->type != TemplateToken::Type::EndGeneration) {
throw unterminated(**start);
}
// Treat as a no-op, as our scope is templates for inference, not training (`{% generation %}` wraps generated tokens for masking).
children.emplace_back(std::move(body));
} else if (auto text_token = dynamic_cast<TextTemplateToken*>(token.get())) { } else if (auto text_token = dynamic_cast<TextTemplateToken*>(token.get())) {
SpaceHandling pre_space = (it - 1) != begin ? (*(it - 2))->post_space : SpaceHandling::Keep; SpaceHandling pre_space = (it - 1) != begin ? (*(it - 2))->post_space : SpaceHandling::Keep;
SpaceHandling post_space = it != end ? (*it)->pre_space : SpaceHandling::Keep; SpaceHandling post_space = it != end ? (*it)->pre_space : SpaceHandling::Keep;
@ -2397,6 +2420,7 @@ private:
|| dynamic_cast<EndFilterTemplateToken*>(token.get()) || dynamic_cast<EndFilterTemplateToken*>(token.get())
|| dynamic_cast<EndIfTemplateToken*>(token.get()) || dynamic_cast<EndIfTemplateToken*>(token.get())
|| dynamic_cast<ElseTemplateToken*>(token.get()) || dynamic_cast<ElseTemplateToken*>(token.get())
|| dynamic_cast<EndGenerationTemplateToken*>(token.get())
|| dynamic_cast<ElifTemplateToken*>(token.get())) { || dynamic_cast<ElifTemplateToken*>(token.get())) {
it--; // unconsume the token it--; // unconsume the token
break; // exit the loop break; // exit the loop