better supports_string_content cap detect

This commit is contained in:
Xuan Son Nguyen
2026-08-15 13:06:26 +02:00
parent 27df9199d1
commit 37e7235645
2 changed files with 59 additions and 4 deletions
+10 -4
View File
@@ -23,7 +23,7 @@ void caps_apply_preserve_reasoning(jinja::context & ctx, bool enabled) {
ctx.set_val("preserve_thinking", mk_val<value_bool>(enabled));
ctx.set_val("clear_thinking", mk_val<value_bool>(!enabled));
ctx.set_val("truncate_history_thinking", mk_val<value_bool>(!enabled));
ctx.set_val("drop_thinking", mk_val<value_bool>(!enabled));
ctx.set_val("drop_thinking", mk_val<value_bool>(!enabled));
}
void caps_apply_reasoning_effort(jinja::context & ctx, const std::string & effort) {
@@ -117,6 +117,8 @@ caps caps_get(jinja::program & prog) {
JJ_DEBUG("%s\n", ">>> Running capability check: typed content");
static const std::string content_marker = "STRING_MARKER";
// case: typed content support
caps_try_execute(
prog,
@@ -125,22 +127,26 @@ caps caps_get(jinja::program & prog) {
return json::array({
{
{"role", "user"},
{"content", "content"}
{"content", content_marker}
}
});
},
nullptr, // ctx_fn
nullptr, // tools_fn
[&](context &, bool success, value & messages, value &, const std::string &) {
[&](context &, bool success, value & messages, value &, const std::string & rendered) {
auto & content = messages->at(0)->at("content");
caps_print_stats(content, "messages[0].content");
if (has_op(content, "selectattr") || has_op(content, "array_access")) {
bool used_as_array = has_op(content, "selectattr") || has_op(content, "array_access");
if (used_as_array) {
// accessed as an array
result.supports_typed_content = true;
}
if (!success) {
// failed to execute with content as string
result.supports_string_content = false;
} else if (used_as_array && rendered.find(content_marker) == std::string::npos) {
// edge case: string may be accessed for checking, but does not appear in the output
result.supports_string_content = false;
}
}
);
+49
View File
@@ -10,6 +10,7 @@
#include "jinja/parser.h"
#include "jinja/lexer.h"
#include "jinja/utils.h"
#include "jinja/caps.h"
#include "testing.h"
@@ -33,6 +34,7 @@ static void test_array_methods(testing & t);
static void test_object_methods(testing & t);
static void test_hasher(testing & t);
static void test_stats(testing & t);
static void test_caps(testing & t);
static void test_string_parts(testing & t);
static void test_fuzzing(testing & t);
@@ -73,6 +75,7 @@ int main(int argc, char *argv[]) {
if (!g_python_mode) {
t.test("hasher", test_hasher);
t.test("stats", test_stats);
t.test("caps", test_caps);
t.test("string parts", test_string_parts);
t.test("fuzzing", test_fuzzing);
}
@@ -2059,6 +2062,52 @@ static void test_stats(testing & t) {
});
}
static void test_caps(testing & t) {
static auto get_caps = [](const std::string & tmpl) -> jinja::caps {
jinja::lexer lexer;
auto lexer_res = lexer.tokenize(tmpl);
jinja::program prog = jinja::parse_from_tokens(lexer_res);
return jinja::caps_get(prog);
};
t.test("string content", [](testing & t) {
auto caps = get_caps(
"{% for message in messages %}"
"{{ message['role'] + ': ' + message['content'] }}"
"{% endfor %}"
);
t.assert_true("supports string content", caps.supports_string_content);
t.assert_true("does not support typed content", !caps.supports_typed_content);
});
t.test("typed content, raises on string", [](testing & t) {
// rendering with string content throws: 'selectattr' is not a String filter
auto caps = get_caps(
"{% for message in messages %}"
"{% for content in message['content'] | selectattr('type', 'equalto', 'text') %}"
"{{ content['text'] }}"
"{% endfor %}"
"{% endfor %}"
);
t.assert_true("does not support string content", !caps.supports_string_content);
t.assert_true("supports typed content", caps.supports_typed_content);
});
t.test("typed content, silently drops string", [](testing & t) {
// rendering with string content does not throw, but content[0]['text'] yields
// undefined for a string, so the message is silently dropped (MiniMax-M1 shape)
auto caps = get_caps(
"{% for message in messages %}"
"{{ message['content'][0]['text'] }}"
"{% endfor %}"
);
t.assert_true("does not support string content", !caps.supports_string_content);
t.assert_true("supports typed content", caps.supports_typed_content);
});
}
static void test_string_parts(testing & t) {
static auto render = [](const std::string & tmpl, const json & vars) -> jinja::string {
jinja::lexer lexer;