This commit is contained in:
Xuan Son Nguyen
2026-08-21 22:22:14 +02:00
parent d1a9e5a8e6
commit ea4b4b2862
25 changed files with 265 additions and 179 deletions
+1
View File
@@ -81,6 +81,7 @@ add_library(${TARGET}
imatrix-loader.cpp
imatrix-loader.h
json-schema-to-grammar.cpp
json-shim.h
json.cpp
json.h
llguidance.cpp
+4 -5
View File
@@ -5,13 +5,12 @@
#include "common.h"
#include "json-schema-to-grammar.h"
#include "log.h"
#include "nlohmann/json.hpp"
#include "peg-parser.h"
#include <stdexcept>
#include <string>
using json = nlohmann::ordered_json;
using json = common_json;
// Helper to iterate over tools/functions
static void foreach_function(const json & tools, const std::function<void(const json &)> & fn) {
@@ -312,7 +311,7 @@ common_peg_parser analyze_tools::build_tool_parser_tag_json(parser_build_context
foreach_function(inputs.tools, [&](const json & tool) {
const auto & func = tool.at("function");
std::string name = func.at("name");
std::string name = func.at("name").get<std::string>();
const auto & schema = func.contains("parameters") ? func.at("parameters") : json::object();
// Build call_id parser based on position (if supported)
@@ -385,13 +384,13 @@ common_peg_parser analyze_tools::build_tool_parser_tag_tagged(parser_build_conte
foreach_function(inputs.tools, [&](const json & tool) {
const auto & func = tool.at("function");
std::string name = func.at("name");
std::string name = func.at("name").get<std::string>();
auto params = func.contains("parameters") ? func.at("parameters") : json::object();
const auto & properties = params.contains("properties") ? params.at("properties") : json::object();
std::set<std::string> required;
if (params.contains("required")) {
params.at("required").get_to(required);
required = params.at("required").get<std::set<std::string>>();
}
auto schema_info = common_schema_info();
+2 -2
View File
@@ -4,7 +4,7 @@
#include "common.h"
#include "jinja/caps.h"
#include "peg-parser.h"
#include "nlohmann/json.hpp"
#include "json.h"
#include <chrono>
#include <optional>
@@ -12,7 +12,7 @@
#include <utility>
#include <vector>
using json = nlohmann::ordered_json;
using json = common_json;
class common_chat_peg_builder;
+5 -5
View File
@@ -4,9 +4,9 @@
#include "chat.h"
#include "common.h"
#include "log.h"
#include "nlohmann/json.hpp"
#include "peg-parser.h"
#include <numeric>
#include <algorithm>
#include <cctype>
#include <ostream>
@@ -17,7 +17,7 @@
#define ANSI_ORANGE "\033[1m\x1b[38;5;214m"
#define ANSI_RED "\033[1m\x1b[38;5;196m"
using json = nlohmann::ordered_json;
using json = common_json;
namespace autoparser {
@@ -929,10 +929,10 @@ void analyze_tools::analyze_tool_call_format_json_native(const std::string & cle
int json_end = clean_haystack.find_last_of('}');
std::string cut = clean_haystack.substr(json_start, json_end - json_start + 1);
json call_struct = json::parse(cut);
auto register_field = [&](const std::string & prefix, const nlohmann::detail::iteration_proxy_value<json::iterator> & subel) {
if (subel.value().is_string() && std::string(subel.value()).find("call0000") != std::string::npos) {
auto register_field = [&](const std::string & prefix, const common_json_entry & subel) {
if (subel.value().is_string() && subel.value().get<std::string>().find("call0000") != std::string::npos) {
format.id_field = !prefix.empty() ? prefix + "." + subel.key() : subel.key();
} else if (subel.value().is_string() && std::string(subel.value()) == fun_name_needle) {
} else if (subel.value().is_string() && subel.value().get<std::string>() == fun_name_needle) {
format.name_field = !prefix.empty() ? prefix + "." + subel.key() : subel.key();
} else if (subel.value().dump().find(arg_name_needle) !=
std::string::npos) { // handle both string and JSON obj variants
+6 -7
View File
@@ -4,12 +4,11 @@
#include "ggml.h"
#include "peg-parser.h"
#include <nlohmann/json.hpp>
#include <cstdint>
#include <functional>
using ordered_json = nlohmann::ordered_json;
using ordered_json = common_json;
static std::string_view trim_trailing_space(std::string_view sv, int max = -1) {
int count = 0;
@@ -489,7 +488,7 @@ common_peg_parser common_chat_peg_builder::standard_constructed_tools(
continue;
}
const auto & function = tool_def.at("function");
std::string name = function.at("name");
std::string name = function.at("name").get<std::string>();
ordered_json params = function.contains("parameters") ? function.at("parameters") : ordered_json::object();
// Build argument parsers
@@ -566,7 +565,7 @@ common_peg_parser common_chat_peg_builder::python_style_tool_calls(
continue;
}
const auto & function = tool_def.at("function");
std::string name = function.at("name");
std::string name = function.at("name").get<std::string>();
ordered_json params = function.contains("parameters") ? function.at("parameters") : ordered_json::object();
auto args = eps();
@@ -641,7 +640,7 @@ common_peg_parser common_chat_peg_builder::build_json_tools_function_is_key(
continue;
}
const auto & function = tool_def.at("function");
std::string name = function.at("name");
std::string name = function.at("name").get<std::string>();
ordered_json params = function.contains("parameters") ? function.at("parameters") : ordered_json::object();
// Build inner object fields
@@ -727,7 +726,7 @@ common_peg_parser common_chat_peg_builder::build_json_tools_nested_keys(
continue;
}
const auto & function = tool_def.at("function");
std::string name = function.at("name");
std::string name = function.at("name").get<std::string>();
ordered_json params = function.contains("parameters") ? function.at("parameters") : ordered_json::object();
auto nested_name = literal("\"" + nested_name_field + "\"") + space() + literal(":") + space() +
@@ -796,7 +795,7 @@ common_peg_parser common_chat_peg_builder::build_json_tools_flat_keys(
continue;
}
const auto & function = tool_def.at("function");
std::string name = function.at("name");
std::string name = function.at("name").get<std::string>();
ordered_json params = function.contains("parameters") ? function.at("parameters") : ordered_json::object();
auto tool_name_ = name_key_parser + space() + literal(":") + space() +
+6 -6
View File
@@ -128,7 +128,7 @@ class common_chat_peg_builder : public common_peg_parser_builder {
// parameters_order: order in which JSON fields should be parsed
common_peg_parser standard_json_tools(const std::string & section_start,
const std::string & section_end,
const nlohmann::ordered_json & tools,
const common_json & tools,
bool parallel_tool_calls,
bool force_tool_calls,
const std::string & name_key = "",
@@ -143,13 +143,13 @@ class common_chat_peg_builder : public common_peg_parser_builder {
// Legacy-compatible helper for building XML/tagged style tool calls
// Used by tests and manual parsers
common_peg_parser standard_constructed_tools(const std::map<std::string, std::string> & markers,
const nlohmann::ordered_json & tools,
const common_json & tools,
bool parallel_tool_calls,
bool force_tool_calls);
// Helper for Python-style function call format: name(arg1="value1", arg2=123)
// Used by LFM2 and similar templates
common_peg_parser python_style_tool_calls(const nlohmann::ordered_json & tools,
common_peg_parser python_style_tool_calls(const common_json & tools,
bool parallel_tool_calls,
bool allow_json_literals);
@@ -158,19 +158,19 @@ class common_chat_peg_builder : public common_peg_parser_builder {
common_peg_parser python_or_json_value();
// Implementation helpers for standard_json_tools — one per JSON tool call layout mode
common_peg_parser build_json_tools_function_is_key(const nlohmann::ordered_json & tools,
common_peg_parser build_json_tools_function_is_key(const common_json & tools,
const std::string & args_key,
const std::string & effective_args_key,
const std::string & call_id_key,
const std::string & gen_call_id_key);
common_peg_parser build_json_tools_nested_keys(const nlohmann::ordered_json & tools,
common_peg_parser build_json_tools_nested_keys(const common_json & tools,
const std::string & effective_name_key,
const std::string & effective_args_key,
const std::string & call_id_key,
const std::string & gen_call_id_key);
common_peg_parser build_json_tools_flat_keys(const nlohmann::ordered_json & tools,
common_peg_parser build_json_tools_flat_keys(const common_json & tools,
const std::string & effective_name_key,
const std::string & effective_args_key,
const std::string & call_id_key,
+42 -43
View File
@@ -14,7 +14,6 @@
#include "jinja/caps.h"
#include "peg-parser.h"
#include "nlohmann/json.hpp"
#include <algorithm>
#include <cstdio>
@@ -31,7 +30,7 @@
#include <utility>
#include <vector>
using json = nlohmann::ordered_json;
using json = common_json;
static std::string format_time(const std::chrono::system_clock::time_point & now, const std::string & format) {
auto time = std::chrono::system_clock::to_time_t(now);
@@ -49,7 +48,7 @@ static json safe_args_parse(const std::string & to_parse) {
}
try {
return json::parse(stripped);
} catch (json::exception & e) {
} catch (const common_json_error & e) {
return stripped;
}
}
@@ -387,14 +386,14 @@ std::vector<common_chat_msg> common_chat_msgs_parse_oaicompat(const json & messa
if (!message.contains("role")) {
throw std::invalid_argument("Missing 'role' in message: " + message.dump());
}
msg.role = message.at("role");
msg.role = message.at("role").get<std::string>();
auto has_content = message.contains("content");
auto has_tool_calls = message.contains("tool_calls");
if (has_content) {
const auto & content = message.at("content");
if (content.is_string()) {
msg.content = content;
msg.content = content.get<std::string>();
} else if (content.is_array()) {
for (const auto & part : content) {
if (!part.contains("type")) {
@@ -405,8 +404,8 @@ std::vector<common_chat_msg> common_chat_msgs_parse_oaicompat(const json & messa
throw std::invalid_argument("Unsupported content part type: " + type.dump());
}
common_chat_msg_content_part msg_part;
msg_part.type = type;
msg_part.text = part.at("text");
msg_part.type = type.get<std::string>();
msg_part.text = part.at("text").get<std::string>();
msg.content_parts.push_back(msg_part);
}
} else if (!content.is_null()) {
@@ -432,15 +431,15 @@ std::vector<common_chat_msg> common_chat_msgs_parse_oaicompat(const json & messa
if (!fc.contains("name")) {
throw std::invalid_argument("Missing tool call name: " + tool_call.dump());
}
tc.name = fc.at("name");
tc.name = fc.at("name").get<std::string>();
const auto & args = fc.at("arguments");
if (args.is_string()) {
tc.arguments = args;
tc.arguments = args.get<std::string>();
} else {
tc.arguments = args.dump();
}
if (tool_call.contains("id")) {
tc.id = tool_call.at("id");
tc.id = tool_call.at("id").get<std::string>();
}
msg.tool_calls.push_back(tc);
}
@@ -451,13 +450,13 @@ std::vector<common_chat_msg> common_chat_msgs_parse_oaicompat(const json & messa
"https://github.com/ggml-org/llama.cpp/issues/12279)");
}
if (message.contains("reasoning_content")) {
msg.reasoning_content = message.at("reasoning_content");
msg.reasoning_content = message.at("reasoning_content").get<std::string>();
}
if (message.contains("name")) {
msg.tool_name = message.at("name");
msg.tool_name = message.at("name").get<std::string>();
}
if (message.contains("tool_call_id")) {
msg.tool_call_id = message.at("tool_call_id");
msg.tool_call_id = message.at("tool_call_id").get<std::string>();
}
msgs.push_back(msg);
@@ -489,17 +488,17 @@ struct messages_inp_normalizer {
json normalized = json::array();
for (const auto & msg : messages) {
json copy = msg;
auto it = copy.find("content");
if (it != copy.end()) {
if (only_typed && it->is_string()) {
*it = json::array({
if (copy.contains("content")) {
json & it = copy.at("content");
if (only_typed && it.is_string()) {
it = json::array({
json{
{"type", "text"},
{"text", it->get<std::string>()},
{"text", it.get<std::string>()},
}
});
} else if (only_string && it->is_array()) {
*it = concat_content_parts(*it);
} else if (only_string && it.is_array()) {
it = concat_content_parts(it);
}
}
normalized.push_back(std::move(copy));
@@ -596,7 +595,7 @@ std::vector<common_chat_tool> common_chat_tools_parse_oaicompat(const json & too
const auto & function = tool.at("function");
result.push_back({
/* .name = */ function.at("name"),
/* .name = */ function.at("name").get<std::string>(),
/* .description = */ function.value("description", ""),
/* .parameters = */ function.value("parameters", json::object()).dump(),
});
@@ -609,7 +608,7 @@ std::vector<common_chat_tool> common_chat_tools_parse_oaicompat(const json & too
return result;
}
common_chat_continuation common_chat_continuation_parse(const nlohmann::ordered_json & value) {
common_chat_continuation common_chat_continuation_parse(const common_json & value) {
if (value.is_boolean() && value.get<bool>()) {
return COMMON_CHAT_CONTINUATION_AUTO;
}
@@ -921,7 +920,7 @@ static void foreach_parameter(const json &
const auto & props = params.at("properties");
std::set<std::string> required;
if (params.contains("required") && params.at("required").is_array()) {
params.at("required").get_to(required);
required = params.at("required").get<std::set<std::string>>();
}
for (const auto & [name, prop] : props.items()) {
bool is_required = (required.find(name) != required.end());
@@ -938,7 +937,7 @@ static std::string common_chat_template_direct_apply_impl(
jinja::context ctx(tmpl.source());
// messages_override is already built for this template, do not touch its content parts
nlohmann::ordered_json inp = nlohmann::ordered_json{
common_json inp = common_json{
{"messages", messages_override.has_value()
? *messages_override
: messages_inp_normalizer(tmpl.original_caps()).normalize(inputs.messages)},
@@ -973,7 +972,7 @@ static std::string common_chat_template_direct_apply_impl(
jinja::caps_apply_reasoning_effort(ctx, reasoning_effort);
}
jinja::global_from_json(ctx, common_json_from_raw(inp), inputs.mark_input);
jinja::global_from_json(ctx, inp, inputs.mark_input);
// render
jinja::runtime runtime(ctx);
@@ -1059,7 +1058,7 @@ static common_chat_params common_chat_params_init_ministral_3(const common_chat_
});
} else if (msg.at("content").is_array()) {
auto blocks = msg.at("content");
content.insert(content.end(), blocks.begin(), blocks.end());
content.insert(blocks);
}
}
@@ -1114,7 +1113,7 @@ static common_chat_params common_chat_params_init_ministral_3(const common_chat_
auto tool_choice = p.choice();
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
std::string name = function.at("name");
std::string name = function.at("name").get<std::string>();
const auto & schema = function.at("parameters");
tool_choice |=
@@ -1222,7 +1221,7 @@ static common_chat_params common_chat_params_init_qwen3_coder(const common_chat_
// 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");
const std::string name = tool.at("function").at("name").get<std::string>();
tool_call_starts.push_back("<function=" + name + ">");
});
@@ -1250,7 +1249,7 @@ static common_chat_params common_chat_params_init_qwen3_coder(const common_chat_
auto tool_choice = p.choice();
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
std::string name = function.at("name");
std::string name = function.at("name").get<std::string>();
auto parameters = function.contains("parameters") ? function.at("parameters") : json::object();
auto schema_info = common_schema_info();
@@ -1441,7 +1440,7 @@ static common_chat_params common_chat_params_init_gpt_oss(const common_chat_temp
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
std::string name = function.at("name");
std::string name = function.at("name").get<std::string>();
const auto & params = function.at("parameters");
auto func_name = p.literal(" to=functions.") + p.tool_name(p.literal(name));
@@ -1607,7 +1606,7 @@ static common_chat_params common_chat_params_init_gemma4(const common_chat_templ
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
std::string name = function.at("name");
std::string name = function.at("name").get<std::string>();
// TODO @aldehir : need to extend json-schema-to-grammar to produce more than JSON rules
// const auto & params = function.at("parameters");
@@ -1706,7 +1705,7 @@ static common_chat_params common_chat_params_init_functionary_v3_2(const common_
auto tool_choice = p.choice();
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
std::string name = function.at("name");
std::string name = function.at("name").get<std::string>();
const auto & schema = function.at("parameters");
// Tool format: >>>function_name\n{json_args}
@@ -1843,7 +1842,7 @@ static common_chat_params common_chat_params_init_kimi_k2(const common_chat_temp
auto tool_choice = p.choice();
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
std::string name = function.at("name");
std::string name = function.at("name").get<std::string>();
const auto & schema = function.at("parameters");
// Match: functions.<name>:<digits>
@@ -2037,7 +2036,7 @@ static common_chat_params common_chat_params_init_gigachat_v3(
auto tool_choice = p.choice();
for (const auto & tool : inputs.tools) {
const auto & function = tool.at("function");
std::string name = function.at("name");
std::string name = function.at("name").get<std::string>();
const auto & schema = function.at("parameters");
auto tool_name = p.json_member("name", "\"" + p.tool_name(p.literal(name)) + "\"");
@@ -2233,13 +2232,13 @@ static common_chat_params common_chat_params_init_deepseek_v3_2(const common_cha
if (has_tool_calls) {
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
std::string name = function.at("name");
std::string name = function.at("name").get<std::string>();
auto params = function.contains("parameters") ? function.at("parameters") : json::object();
const auto & props = params.contains("properties") ? params.at("properties") : json::object();
std::set<std::string> required;
if (params.contains("required")) {
params.at("required").get_to(required);
required = params.at("required").get<std::set<std::string>>();
}
auto schema_info = common_schema_info();
@@ -2468,7 +2467,7 @@ static common_chat_params common_chat_params_init_kimi_k3(const common_chat_temp
auto tool_choices = p.choice();
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
std::string name = function.at("name");
std::string name = function.at("name").get<std::string>();
const json schema = function.contains("parameters") ? function.at("parameters") : json::object();
// arguments come one tag per key, with the JSON type in a type="..."
@@ -2789,7 +2788,7 @@ static common_chat_params common_chat_params_init_minimax_m3(const common_chat_t
auto tool_choice = p.choice();
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
std::string name = function.at("name");
std::string name = function.at("name").get<std::string>();
auto params = function.contains("parameters") ? function.at("parameters") : json::object();
auto schema_info = common_schema_info();
@@ -2861,7 +2860,7 @@ static common_chat_params common_chat_params_init_minimax_m3(const common_chat_t
std::set<std::string> required;
if (schema.contains("required")) {
schema.at("required").get_to(required);
required = schema.at("required").get<std::set<std::string>>();
}
std::vector<common_peg_parser> required_elements;
@@ -2973,10 +2972,10 @@ static void system_message_not_supported(json & messages) {
auto & second_msg = messages[1];
second_msg["content"] = first_msg.at("content").get<std::string>()
+ "\n" + second_msg.at("content").get<std::string>();
messages.erase(messages.begin());
messages.erase(0);
} else {
LOG_WRN("Removing system prompt due to template not supporting system role\n");
messages.erase(messages.begin());
messages.erase(0);
}
}
}
@@ -3243,7 +3242,7 @@ static common_chat_params common_chat_params_init_minicpm5(const common_chat_tem
auto tool_choice = p.choice();
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
const std::string name = function.at("name");
const std::string name = function.at("name").get<std::string>();
auto params = function.contains("parameters") ? function.at("parameters") : json::object();
auto args = p.eps();
@@ -3389,7 +3388,7 @@ static common_chat_params common_chat_params_init_muse_glimmer(const common_chat
auto tool_choice = p.choice();
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
const std::string name = function.at("name");
const std::string name = function.at("name").get<std::string>();
auto params = function.contains("parameters") ? function.at("parameters") : json::object();
auto args = p.eps();
+9 -9
View File
@@ -8,7 +8,7 @@
#include "jinja/runtime.h"
#include "jinja/caps.h"
#include "nlohmann/json_fwd.hpp"
#include "json.h"
#include <chrono>
#include <functional>
@@ -86,7 +86,7 @@ struct common_chat_msg {
std::string tool_name;
std::string tool_call_id;
nlohmann::ordered_json to_json_oaicompat(bool concat_typed_text = false) const;
common_json to_json_oaicompat(bool concat_typed_text = false) const;
std::string render_content(const std::string & delimiter = "\n\n") const;
@@ -210,7 +210,7 @@ struct common_chat_msg_delimiters {
// split tokens into message spans. skips maps a start index to a length of a region to jump over without matching
common_chat_msg_spans split(const llama_tokens & tokens, const std::map<size_t, size_t> & skips = {}) const;
nlohmann::ordered_json to_json() const;
common_json to_json() const;
};
struct common_chat_tool {
@@ -349,16 +349,16 @@ common_chat_tool_choice common_chat_tool_choice_parse_oaicompat(const std::strin
bool common_chat_templates_support_enable_thinking(const common_chat_templates * chat_templates);
// Parses a JSON array of messages in OpenAI's chat completion API format.
std::vector<common_chat_msg> common_chat_msgs_parse_oaicompat(const nlohmann::ordered_json & messages);
std::vector<common_chat_msg> common_chat_msgs_parse_oaicompat(const common_json & messages);
std::vector<common_chat_tool> common_chat_tools_parse_oaicompat(const nlohmann::ordered_json & tools);
std::vector<common_chat_tool> common_chat_tools_parse_oaicompat(const common_json & tools);
common_chat_continuation common_chat_continuation_parse(const nlohmann::ordered_json & value);
common_chat_continuation common_chat_continuation_parse(const common_json & value);
// DEPRECATED: only used in tests
nlohmann::ordered_json common_chat_msgs_to_json_oaicompat(const std::vector<common_chat_msg> & msgs, bool concat_typed_text = false);
common_json common_chat_msgs_to_json_oaicompat(const std::vector<common_chat_msg> & msgs, bool concat_typed_text = false);
nlohmann::ordered_json common_chat_tools_to_json_oaicompat(const std::vector<common_chat_tool> & tools);
common_json common_chat_tools_to_json_oaicompat(const std::vector<common_chat_tool> & tools);
// get template caps, useful for reporting to server /props endpoint
std::map<std::string, bool> common_chat_templates_get_caps(const common_chat_templates * chat_templates);
@@ -385,4 +385,4 @@ struct common_chat_prompt_preset {
common_chat_prompt_preset common_chat_get_asr_prompt(const common_chat_templates * chat_templates);
common_chat_msg_delimiters common_chat_msg_delimiters_parse(const nlohmann::ordered_json & delimiters);
common_chat_msg_delimiters common_chat_msg_delimiters_parse(const common_json & delimiters);
+14 -12
View File
@@ -1,4 +1,6 @@
#include "json-schema-to-grammar.h"
// the grammar builder walks the schema with the library API
#include "json-shim.h"
#include "common.h"
#include <nlohmann/json.hpp>
@@ -12,7 +14,7 @@
#include <unordered_set>
#include <vector>
using json = nlohmann::ordered_json;
using json = common_json;
static std::string build_repetition(const std::string & item_rule, int min_items, int max_items, const std::string & separator_rule = "") {
auto has_max = max_items != std::numeric_limits<int>::max();
@@ -843,7 +845,7 @@ public:
}
} else if (n.is_object()) {
if (n.contains("$ref")) {
std::string ref = n["$ref"];
std::string ref = n["$ref"].get<std::string>();
if (_refs.find(ref) == _refs.end()) {
json target;
if (ref.find("https://") == 0) {
@@ -914,7 +916,7 @@ public:
std::string rule_name = is_reserved_name(name) ? name + "-" : name.empty() ? "root" : name;
if (schema.contains("$ref")) {
return _add_rule(rule_name, _resolve_ref(schema["$ref"]));
return _add_rule(rule_name, _resolve_ref(schema["$ref"].get<std::string>()));
}
if (schema.contains("oneOf") || schema.contains("anyOf")) {
std::vector<json> alt_schemas = schema.contains("oneOf") ? schema["oneOf"].get<std::vector<json>>() : schema["anyOf"].get<std::vector<json>>();
@@ -968,7 +970,7 @@ public:
const std::string& hybrid_name = name;
std::function<void(const json &, bool)> add_component = [&](const json & comp_schema, bool is_required) {
if (comp_schema.contains("$ref")) {
add_component(_refs[comp_schema["$ref"]], is_required);
add_component(_refs[comp_schema["$ref"].get<std::string>()], is_required);
} else if (comp_schema.contains("properties")) {
for (const auto & prop : comp_schema["properties"].items()) {
properties.emplace_back(prop.key(), prop.value());
@@ -1031,7 +1033,7 @@ public:
return _add_rule(rule_name, "\"[\" space " + build_repetition(item_rule_name, min_items, max_items, "\",\" space") + " space \"]\"");
}
if ((schema_type.is_null() || schema_type == "string") && schema.contains("pattern")) {
return _visit_pattern(schema["pattern"], rule_name);
return _visit_pattern(schema["pattern"].get<std::string>(), rule_name);
}
if ((schema_type.is_null() || schema_type == "string") && std::regex_match(schema_format, std::regex("^uuid[1-5]?$"))) {
return _add_primitive(rule_name == "root" ? "root" : schema_format, PRIMITIVE_RULES.at("uuid"));
@@ -1111,7 +1113,7 @@ common_schema_info::~common_schema_info() = default;
common_schema_info::common_schema_info(common_schema_info &&) noexcept = default;
common_schema_info & common_schema_info::operator=(common_schema_info &&) noexcept = default;
void common_schema_info::resolve_refs(nlohmann::ordered_json & schema) {
void common_schema_info::resolve_refs(common_json & schema) {
impl_->resolve_refs(schema, "");
}
@@ -1119,7 +1121,7 @@ void common_schema_info::resolve_refs(nlohmann::ordered_json & schema) {
// Some models emit raw string values rather than JSON-encoded strings for string parameters.
// If any branch of the schema (via oneOf, anyOf, $ref, etc.) permits a string, this returns
// true, allowing callers to handle the value as a raw string for simplicity.
bool common_schema_info::resolves_to_string(const nlohmann::ordered_json & schema) {
bool common_schema_info::resolves_to_string(const common_json & schema) {
std::unordered_set<std::string> visited_refs;
std::function<bool(const json &)> check = [&](const json & s) -> bool {
@@ -1129,7 +1131,7 @@ bool common_schema_info::resolves_to_string(const nlohmann::ordered_json & schem
// Handle $ref
if (s.contains("$ref")) {
const std::string & ref = s["$ref"];
const std::string ref = s["$ref"].get<std::string>();
if (visited_refs.find(ref) != visited_refs.end()) {
// Circular reference, assume not a string to be safe
return false;
@@ -1212,7 +1214,7 @@ bool common_schema_info::resolves_to_string(const nlohmann::ordered_json & schem
// Check format - many formats imply string
if (s.contains("format")) {
const std::string & fmt = s["format"];
const std::string fmt = s["format"].get<std::string>();
if (fmt == "date" || fmt == "time" || fmt == "date-time" ||
fmt == "uri" || fmt == "email" || fmt == "hostname" ||
fmt == "ipv4" || fmt == "ipv6" || fmt == "uuid" ||
@@ -1236,7 +1238,7 @@ std::string json_schema_to_grammar(const common_json & schema, bool force_gbnf)
(void)force_gbnf;
#endif // LLAMA_USE_LLGUIDANCE
return build_grammar([&](const common_grammar_builder & callbacks) {
auto copy = common_json_raw<nlohmann::ordered_json>(schema);
auto copy = common_json_raw<common_json>(schema);
callbacks.resolve_refs(copy);
callbacks.add_schema("", copy);
});
@@ -1248,10 +1250,10 @@ std::string build_grammar(const std::function<void(const common_grammar_builder
/* .add_rule = */ [&](const std::string & name, const std::string & rule) {
return converter._add_rule(name, rule);
},
/* .add_schema = */ [&](const std::string & name, const nlohmann::ordered_json & schema) {
/* .add_schema = */ [&](const std::string & name, const common_json & schema) {
return converter.visit(schema, name == "root" ? "" : name);
},
/* .resolve_refs = */ [&](nlohmann::ordered_json & schema) {
/* .resolve_refs = */ [&](common_json & schema) {
converter.resolve_refs(schema, "");
}
};
+4 -5
View File
@@ -2,7 +2,6 @@
#include "json.h"
#include <nlohmann/json_fwd.hpp>
#include <functional>
#include <memory>
@@ -26,14 +25,14 @@ class common_schema_info {
common_schema_info(common_schema_info &&) noexcept;
common_schema_info & operator=(common_schema_info &&) noexcept;
void resolve_refs(nlohmann::ordered_json & schema);
bool resolves_to_string(const nlohmann::ordered_json & schema);
void resolve_refs(common_json & schema);
bool resolves_to_string(const common_json & schema);
};
struct common_grammar_builder {
std::function<std::string(const std::string &, const std::string &)> add_rule;
std::function<std::string(const std::string &, const nlohmann::ordered_json &)> add_schema;
std::function<void(nlohmann::ordered_json &)> resolve_refs;
std::function<std::string(const std::string &, const common_json &)> add_schema;
std::function<void(common_json &)> resolve_refs;
};
struct common_grammar_options {
+16
View File
@@ -0,0 +1,16 @@
#pragma once
// converts between common_json and the backing JSON library
//
// include this only in a cpp file that touches an internal component of the library,
// never in a header. every use here is a place to fix if the library changes.
#include "json.h"
template <typename T> T & common_json_raw(common_json & json);
template <typename T> const T & common_json_raw(const common_json & 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 & common_json_ref_from_raw(T & json);
+54 -1
View File
@@ -1,4 +1,6 @@
#include "json.h"
// defines the shim
#include "json-shim.h"
#include "ggml.h"
@@ -80,7 +82,8 @@ 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(const std::vector<std::string> & vals) : type(VAL_JSON) {
template <typename T>
common_json_value::common_json_value(const std::vector<T> & vals) : type(VAL_JSON) {
common_json out = common_json::array();
for (const auto & val : vals) {
@@ -90,6 +93,23 @@ common_json_value::common_json_value(const std::vector<std::string> & vals) : ty
val_json = std::make_shared<common_json>(std::move(out));
}
// a vector value is usable only for the types below
// note: std::vector<bool> is not here, its proxy reference does not convert
#define COMMON_JSON_VEC(...) template common_json_value::common_json_value(const std::vector<__VA_ARGS__> &);
COMMON_JSON_VEC(int)
COMMON_JSON_VEC(unsigned int)
COMMON_JSON_VEC(long)
COMMON_JSON_VEC(unsigned long)
COMMON_JSON_VEC(long long)
COMMON_JSON_VEC(unsigned long long)
COMMON_JSON_VEC(float)
COMMON_JSON_VEC(double)
COMMON_JSON_VEC(std::string)
COMMON_JSON_VEC(common_json)
#undef COMMON_JSON_VEC
common_json_value::common_json_value(std::initializer_list<common_json_item> items) :
type(VAL_JSON), val_json(std::make_shared<common_json>(items)) {}
@@ -143,6 +163,14 @@ common_json common_json::parse(const std::string & text) {
}
}
common_json common_json::parse_no_throw(const std::string & text) {
return common_json_from_raw(ordered_json::parse(text, nullptr, false));
}
bool common_json::is_discarded() const {
return as_json(this).is_discarded();
}
common_json common_json::array() {
return common_json_from_raw(ordered_json::array());
}
@@ -161,6 +189,10 @@ common_json common_json::object() {
return common_json();
}
common_json common_json::object(std::initializer_list<common_json_item> items) {
return common_json(items);
}
common_json common_json::make(const common_json_value & val) {
return common_json(val);
}
@@ -204,6 +236,10 @@ const common_json & common_json::front() const { return as_common(as_json(this).
common_json & common_json::back() { return as_common(as_json(this).back()); }
const common_json & common_json::back() const { return as_common(as_json(this).back()); }
void common_json::clear() {
as_json(this).clear();
}
void common_json::erase(const std::string & key) {
as_json(this).erase(key);
}
@@ -224,6 +260,22 @@ void common_json::push_back(const common_json_value & val) {
as_json(this).push_back(to_json(val));
}
void common_json::push_back(std::initializer_list<common_json_item> items) {
common_json val(items);
as_json(this).push_back(as_json(&val));
}
size_t common_json::count(const std::string & key) const {
return as_json(this).count(key);
}
void common_json::insert(const common_json & vals) {
ordered_json & self = as_json(this);
self.insert(self.end(), as_json(&vals).begin(), as_json(&vals).end());
}
std::string common_json::dump(int indent) const {
return as_json(this).dump(indent);
}
@@ -283,5 +335,6 @@ COMMON_JSON_GET(double)
COMMON_JSON_GET(std::string)
COMMON_JSON_GET(std::vector<std::string>)
COMMON_JSON_GET(std::set<std::string>)
COMMON_JSON_GET(std::vector<int>)
#undef COMMON_JSON_GET
+31 -13
View File
@@ -8,6 +8,7 @@
#include <cstddef>
#include <cstdint>
#include <initializer_list>
#include <iterator>
#include <memory>
#include <stdexcept>
#include <string>
@@ -53,7 +54,8 @@ struct common_json_value {
common_json_value(std::string val) : type(VAL_STRING), val_string(std::move(val)) {}
common_json_value(const char * val);
common_json_value(const common_json & val);
common_json_value(const std::vector<std::string> & vals);
// only for the types instantiated in json.cpp, the rest fails at link time
template <typename T> common_json_value(const std::vector<T> & vals);
// nested object, e.g. {"fn", {{"name", "x"}}}
common_json_value(std::initializer_list<common_json_item> items);
@@ -95,6 +97,11 @@ class common_json {
// direct, a value would need two conversions in a row
common_json(std::nullptr_t);
// one step, so that "abc" or a vector can go straight into a common_json
template <typename T, typename std::enable_if<!std::is_same<typename std::decay<T>::type, common_json>::value &&
!std::is_same<typename std::decay<T>::type, common_json_value>::value, int>::type = 0>
common_json(T && val) : common_json(common_json_value(std::forward<T>(val))) {}
common_json & operator=(const common_json & other);
common_json & operator=(common_json && other) noexcept;
@@ -103,9 +110,15 @@ class common_json {
// throws common_json_error if the text is not valid JSON
static common_json parse(const std::string & text);
// gives a discarded value instead of throwing, check it with is_discarded()
static common_json parse_no_throw(const std::string & text);
bool is_discarded() const;
static common_json array();
static common_json array(std::initializer_list<common_json_value> vals);
static common_json object();
static common_json object(std::initializer_list<common_json_item> items);
// holds a single value, e.g. make("abc").dump() gives "\"abc\""
static common_json make(const common_json_value & val);
@@ -143,6 +156,8 @@ class common_json {
common_json & back();
const common_json & back() const;
void clear();
void erase(const std::string & key);
void erase(size_t idx);
@@ -162,6 +177,15 @@ class common_json {
void set(const common_json_item & item);
void push_back(const common_json_value & val);
// appends one object, e.g. push_back({{"a", 1}})
void push_back(std::initializer_list<common_json_item> items);
// 1 if the key is there, 0 if not
size_t count(const std::string & key) const;
// appends every value of another array
void insert(const common_json & vals);
// a common_json goes through the copy assignment above, everything else becomes a value
template <typename T, typename std::enable_if<!std::is_same<typename std::decay<T>::type, common_json>::value, int>::type = 0>
common_json & operator=(T && val) {
@@ -177,6 +201,12 @@ class common_json {
// walks an array by index, or an object in insertion order
class iterator {
public:
using iterator_category = std::forward_iterator_tag;
using value_type = common_json;
using difference_type = std::ptrdiff_t;
using pointer = common_json *;
using reference = common_json &;
iterator(common_json * node, size_t idx) : node(node), idx(idx) {}
common_json & operator*() const;
@@ -247,15 +277,3 @@ class common_json {
};
using common_json_entry = common_json::items_view::entry;
// bridge for code that still uses internal component from nlohmann::json
// usage: common_json_raw<nlohmann::ordered_json>(j)
// TODO: maybe completely remove this in the future
template <typename T> T & common_json_raw(common_json & json);
template <typename T> const T & common_json_raw(const common_json & 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 & common_json_ref_from_raw(T & json);
+16 -13
View File
@@ -1,4 +1,6 @@
#include "peg-parser.h"
// the interface takes common_json, the parser internals stay on the library
#include "json-shim.h"
#include "common.h"
#include "json-schema-to-grammar.h"
@@ -1120,8 +1122,8 @@ common_peg_parser common_peg_parser_builder::chars(const std::string & classes,
return wrap(arena_.add_parser(common_peg_chars_parser{classes, ranges, negated, min, max}));
}
common_peg_parser common_peg_parser_builder::schema(const common_peg_parser & p, const std::string & name, const nlohmann::ordered_json & schema, bool raw) {
return wrap(arena_.add_parser(common_peg_schema_parser{p.id(), name, std::make_shared<nlohmann::ordered_json>(schema), raw}));
common_peg_parser common_peg_parser_builder::schema(const common_peg_parser & p, const std::string & name, const common_json & schema, bool raw) {
return wrap(arena_.add_parser(common_peg_schema_parser{p.id(), name, std::make_shared<common_json>(schema), raw}));
}
common_peg_parser common_peg_parser_builder::rule(const std::string & name, const common_peg_parser & p, bool trigger) {
@@ -1805,8 +1807,8 @@ void common_peg_arena::build_grammar(const common_grammar_builder & builder, boo
}
}
static nlohmann::json serialize_parser_variant(const common_peg_parser_variant & variant) {
using json = nlohmann::json;
static nlohmann::ordered_json serialize_parser_variant(const common_peg_parser_variant & variant) {
using json = nlohmann::ordered_json;
return std::visit([](const auto & p) -> json {
using T = std::decay_t<decltype(p)>;
@@ -1860,7 +1862,7 @@ static nlohmann::json serialize_parser_variant(const common_peg_parser_variant &
{"type", "schema"},
{"child", p.child},
{"name", p.name},
{"schema", p.schema ? *p.schema : nullptr},
{"schema", p.schema ? common_json_raw<nlohmann::ordered_json>(*p.schema) : nlohmann::ordered_json(nullptr)},
{"raw", p.raw}
};
} else if constexpr (std::is_same_v<T, common_peg_rule_parser>) {
@@ -1888,19 +1890,19 @@ static nlohmann::json serialize_parser_variant(const common_peg_parser_variant &
}, variant);
}
nlohmann::json common_peg_arena::to_json() const {
auto parsers = nlohmann::json::array();
common_json common_peg_arena::to_json() const {
auto parsers = nlohmann::ordered_json::array();
for (const auto & parser : parsers_) {
parsers.push_back(serialize_parser_variant(parser));
}
return nlohmann::json{
return common_json_from_raw(nlohmann::ordered_json{
{"parsers", parsers},
{"rules", rules_},
{"root", root_}
};
});
}
static common_peg_parser_variant deserialize_parser_variant(const nlohmann::json & j) {
static common_peg_parser_variant deserialize_parser_variant(const nlohmann::ordered_json & j) {
if (!j.contains("type") || !j["type"].is_string()) {
throw std::runtime_error("Parser variant JSON missing or invalid 'type' field");
}
@@ -2007,7 +2009,7 @@ static common_peg_parser_variant deserialize_parser_variant(const nlohmann::json
parser.child = j["child"].get<common_peg_parser_id>();
parser.name = j["name"];
if (!j["schema"].is_null()) {
parser.schema = std::make_shared<nlohmann::ordered_json>(j["schema"]);
parser.schema = std::make_shared<common_json>(common_json_from_raw(j["schema"]));
}
parser.raw = j["raw"].get<bool>();
return parser;
@@ -2069,7 +2071,8 @@ static common_peg_parser_variant deserialize_parser_variant(const nlohmann::json
throw std::runtime_error("Unknown parser type: " + type);
}
common_peg_arena common_peg_arena::from_json(const nlohmann::json & j) {
common_peg_arena common_peg_arena::from_json(const common_json & j_in) {
const nlohmann::ordered_json & j = common_json_raw<nlohmann::ordered_json>(j_in);
if (!j.contains("parsers") || !j["parsers"].is_array()) {
throw std::runtime_error("JSON missing or invalid 'parsers' array");
}
@@ -2109,7 +2112,7 @@ std::string common_peg_arena::save() const {
}
void common_peg_arena::load(const std::string & data) {
*this = from_json(nlohmann::json::parse(data));
*this = from_json(common_json::parse(data));
}
common_peg_arena build_peg_parser(const std::function<common_peg_parser(common_peg_parser_builder & builder)> & fn) {
+5 -5
View File
@@ -1,6 +1,6 @@
#pragma once
#include <nlohmann/json_fwd.hpp>
#include "json.h"
#include <memory>
#include <set>
@@ -245,7 +245,7 @@ struct common_peg_until_parser {
struct common_peg_schema_parser {
common_peg_parser_id child;
std::string name;
std::shared_ptr<nlohmann::ordered_json> schema;
std::shared_ptr<common_json> schema;
// Indicates if the GBNF should accept a raw string that matches the schema.
bool raw;
@@ -332,8 +332,8 @@ class common_peg_arena {
std::string dump(common_peg_parser_id id) const;
nlohmann::json to_json() const;
static common_peg_arena from_json(const nlohmann::json & j);
common_json to_json() const;
static common_peg_arena from_json(const common_json & j);
std::string save() const;
void load(const std::string & data);
@@ -490,7 +490,7 @@ class common_peg_parser_builder {
// Wraps a parser with JSON schema metadata for grammar generation.
// Used internally to convert JSON schemas to GBNF grammar rules.
common_peg_parser schema(const common_peg_parser & p, const std::string & name, const nlohmann::ordered_json & schema, bool raw = false);
common_peg_parser schema(const common_peg_parser & p, const std::string & name, const common_json & schema, bool raw = false);
// Creates a named rule, stores it in the grammar, and returns a ref.
// If trigger=true, marks this rule as an entry point for lazy grammar generation.