adapt jinja

This commit is contained in:
Xuan Son Nguyen
2026-08-21 18:51:09 +02:00
parent 7a3360f6d8
commit f262180084
7 changed files with 57 additions and 23 deletions
+2 -1
View File
@@ -6,6 +6,7 @@
#include "common.h"
#include "ggml.h"
#include "json-schema-to-grammar.h"
#include "json.h"
#include "log.h"
#include "jinja/value.h"
@@ -972,7 +973,7 @@ static std::string common_chat_template_direct_apply_impl(
jinja::caps_apply_reasoning_effort(ctx, reasoning_effort);
}
jinja::global_from_json(ctx, inp, inputs.mark_input);
jinja::global_from_json(ctx, common_json_ref_from_raw(inp), inputs.mark_input);
// render
jinja::runtime runtime(ctx);
+1 -1
View File
@@ -7,7 +7,7 @@ The implementation can be found in the `common/jinja` directory.
## Key Features
- Input marking: security against special token injection
- Decoupled from `nlohmann::json`: this dependency is only used for JSON-to-internal type translation and is completely optional
- Decoupled from the JSON library: `common_json` is only used for JSON-to-internal type translation and is completely optional
- Minimal primitive types: int, float, bool, string, array, object, none, undefined
- Detailed logging: allow source tracing on error
- Clean architecture: workarounds are applied to input data before entering the runtime (see `common/chat.cpp`)
+3 -3
View File
@@ -4,14 +4,14 @@
// note: the json dependency is only for defining input in a convenient way
// we can remove it in the future when we figure out a better way to define inputs using jinja::value
#include <nlohmann/json.hpp>
#include "json.h"
#include <functional>
#include <sstream>
#define FILENAME "jinja-caps"
using json = nlohmann::ordered_json;
using json = common_json;
namespace jinja {
@@ -370,7 +370,7 @@ caps caps_get(jinja::program & prog) {
caps_try_execute(
prog,
[&]() {
json args = json(R"({"arg": "value"})");
json args = json::make(R"({"arg": "value"})");
if (result.supports_object_arguments) {
args = json{{"arg", "value"}};
}
+12 -10
View File
@@ -3,7 +3,7 @@
#include "value.h"
// for converting from JSON to jinja values
#include <nlohmann/json.hpp>
#include "json.h"
#include <sstream>
#include <string>
@@ -1355,7 +1355,7 @@ const func_builtins & value_undefined_t::get_builtins() const {
//////////////////////////////////
static value from_json(const nlohmann::ordered_json & j, bool mark_input) {
static value from_json(const common_json_ref & j, bool mark_input) {
if (j.is_null()) {
return mk_val<value_none>();
} else if (j.is_boolean()) {
@@ -1378,8 +1378,8 @@ static value from_json(const nlohmann::ordered_json & j, bool mark_input) {
return arr;
} else if (j.is_object()) {
auto obj = mk_val<value_object>();
for (auto it = j.begin(); it != j.end(); ++it) {
obj->insert(it.key(), from_json(it.value(), mark_input));
for (const auto & [key, val] : j.items()) {
obj->insert(key, from_json(val, mark_input));
}
return obj;
} else {
@@ -1451,18 +1451,20 @@ bool value_compare(const value & a, const value & b, value_compare_op op) {
return result;
}
template<>
void global_from_json(context & ctx, const nlohmann::ordered_json & json_obj, bool mark_input) {
// printf("global_from_json: %s\n" , json_obj.dump(2).c_str());
template<typename T_JSON>
void global_from_json(context & ctx, const T_JSON & json_obj, bool mark_input) {
if (json_obj.is_null() || !json_obj.is_object()) {
throw std::runtime_error("global_from_json: input JSON value must be an object");
}
for (auto it = json_obj.begin(); it != json_obj.end(); ++it) {
JJ_DEBUG("global_from_json: setting key '%s'", it.key().c_str());
ctx.set_val(it.key(), from_json(it.value(), mark_input));
for (const auto & [key, val] : json_obj.items()) {
JJ_DEBUG("global_from_json: setting key '%s'", key.c_str());
ctx.set_val(key, from_json(val, mark_input));
}
}
template void global_from_json<common_json> (context &, const common_json &, bool);
template void global_from_json<common_json_ref>(context &, const common_json_ref &, bool);
// recursively convert value to JSON string
// TODO: avoid circular references
static void value_to_json_internal(std::ostringstream & oss, const value & val, int curr_lvl, int indent, const std::string_view item_sep, const std::string_view key_sep) {
+1 -1
View File
@@ -86,7 +86,7 @@ struct context; // forward declaration
// marking input can be useful for tracking data provenance
// and preventing template injection attacks
//
// Note: T_JSON can be nlohmann::ordered_json
// Note: T_JSON can be common_json or common_json_ref
template<typename T_JSON>
void global_from_json(context & ctx, const T_JSON & json_obj, bool mark_input);
+19 -4
View File
@@ -15,10 +15,6 @@ static ordered_json & as_json(common_json_node * node) {
return *reinterpret_cast<ordered_json *>(node);
}
static const ordered_json & as_json(const common_json_node * node) {
return *reinterpret_cast<const ordered_json *>(node);
}
static common_json_node * as_node(ordered_json * json) {
return reinterpret_cast<common_json_node *>(json);
}
@@ -59,10 +55,15 @@ template <typename T> common_json common_json_from_raw(const T & json) {
return make_json(json);
}
template <typename T> common_json_ref common_json_ref_from_raw(T & json) {
return common_json_ref(as_node(&json));
}
// the bridge is usable only for the type below
template ordered_json & common_json_raw<ordered_json>(common_json_ref &);
template const ordered_json & common_json_raw<ordered_json>(const common_json_ref &);
template common_json common_json_from_raw<ordered_json>(const ordered_json &);
template common_json_ref common_json_ref_from_raw<ordered_json>(ordered_json &);
common_json_value::common_json_value(const char * val) {
if (val) {
@@ -76,6 +77,9 @@ common_json_value::common_json_value(const char * val) {
common_json_value::common_json_value(const common_json & val) :
type(VAL_JSON), val_json(std::make_shared<common_json>(val)) {}
common_json_value::common_json_value(std::initializer_list<common_json_item> items) :
type(VAL_JSON), val_json(std::make_shared<common_json>(items)) {}
common_json_value::common_json_value(const common_json_ref & val) :
type(VAL_JSON), val_json(std::make_shared<common_json>(make_json(as_json(val.get_node())))) {}
@@ -86,6 +90,7 @@ bool common_json_ref::is_string() const { return as_json(node).is_string
bool common_json_ref::is_boolean() const { return as_json(node).is_boolean(); }
bool common_json_ref::is_number() const { return as_json(node).is_number(); }
bool common_json_ref::is_number_integer() const { return as_json(node).is_number_integer(); }
bool common_json_ref::is_number_float() const { return as_json(node).is_number_float(); }
bool common_json_ref::empty() const { return as_json(node).empty(); }
size_t common_json_ref::size() const { return as_json(node).size(); }
@@ -199,6 +204,16 @@ common_json common_json::array() {
return make_json(ordered_json::array());
}
common_json common_json::array(std::initializer_list<common_json_value> vals) {
ordered_json out = ordered_json::array();
for (const auto & val : vals) {
out.push_back(to_json(val));
}
return make_json(out);
}
common_json common_json::object() {
return common_json();
}
+19 -3
View File
@@ -1,6 +1,7 @@
#pragma once
// JSON object, it works without the need to include a JSON library header
// the underlay library is pimpl, it should never be exposed here
// note: object keys keep the order in which they are added
#include <cstddef>
@@ -15,6 +16,9 @@
class common_json;
class common_json_ref;
// common_json_value holds a list of these, and each of them holds a value, so one must come first
struct common_json_item;
// one value of the backing library, only json.cpp knows what it is
struct common_json_node;
@@ -57,6 +61,9 @@ struct common_json_value {
common_json_value(const common_json & val);
common_json_value(const common_json_ref & val);
// nested object, e.g. {"fn", {{"name", "x"}}}
common_json_value(std::initializer_list<common_json_item> items);
template <typename T, typename std::enable_if<std::is_integral<T>::value && !std::is_same<T, bool>::value, int>::type = 0>
common_json_value(T val) : type(std::is_signed<T>::value ? VAL_INT : VAL_UINT) {
if (std::is_signed<T>::value) {
@@ -77,6 +84,10 @@ struct common_json_item {
template <typename T>
common_json_item(std::string key, T && val) :
key(std::move(key)), val(std::forward<T>(val)) {}
// a braced list cannot deduce T, so it needs its own overload
common_json_item(std::string key, std::initializer_list<common_json_item> items) :
key(std::move(key)), val(items) {}
};
// view to a value owned by a common_json, it goes stale if the owner gets a new key
@@ -96,6 +107,7 @@ class common_json_ref {
bool is_boolean() const;
bool is_number() const;
bool is_number_integer() const;
bool is_number_float() const;
bool empty() const;
size_t size() const;
@@ -170,9 +182,9 @@ class common_json_ref {
std::pair<std::string, common_json_ref> operator*() const;
iterator & operator++() {
idx++;
return *this;
}
idx++;
return *this;
}
bool operator!=(const iterator & other) const { return idx != other.idx; }
@@ -215,6 +227,7 @@ class common_json : public common_json_ref {
static common_json parse(const std::string & text);
static common_json array();
static common_json array(std::initializer_list<common_json_value> vals);
static common_json object();
// holds a single value, e.g. make("abc").dump() gives "\"abc\""
@@ -232,3 +245,6 @@ template <typename T> T & common_json_raw(common_json_ref & json);
template <typename T> const T & common_json_raw(const common_json_ref & json);
template <typename T> common_json common_json_from_raw(const T & json);
// view over a value of the backing library, it does not copy
template <typename T> common_json_ref common_json_ref_from_raw(T & json);