From 08c403efcde485b88a169b05c65eb50c10c270b8 Mon Sep 17 00:00:00 2001 From: Piotr Wilkin Date: Tue, 3 Feb 2026 01:39:00 +0100 Subject: [PATCH] Quick vibe-coded fix for proper object printing --- common/chat-diff-analyzer.cpp | 4 +++- common/chat.cpp | 2 +- common/jinja/value.h | 11 ++++++++++- tests/test-chat.cpp | 5 +++-- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/common/chat-diff-analyzer.cpp b/common/chat-diff-analyzer.cpp index 6afb9342c2..11aa9e3175 100644 --- a/common/chat-diff-analyzer.cpp +++ b/common/chat-diff-analyzer.cpp @@ -192,6 +192,7 @@ std::optional differential_analyzer::compare_variants( if (params_modifier) { params_modifier(params_B); } + // Apply template to both variants std::string output_A = apply_template(tmpl, params_A); @@ -683,7 +684,8 @@ void differential_analyzer::analyze_tool_call_format_json_native(const std::stri // we might not have the typical OpenAI tool calling structure int json_start = clean_haystack.find_first_of('{'); int json_end = clean_haystack.find_last_of('}'); - json call_struct = json::parse(clean_haystack.substr(json_start, json_end - json_start + 1)); + 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 & subel) { if (subel.value().is_string() && std::string(subel.value()).find("call0000") != std::string::npos) { diff --git a/common/chat.cpp b/common/chat.cpp index bca66132c6..ac775296d7 100644 --- a/common/chat.cpp +++ b/common/chat.cpp @@ -116,7 +116,7 @@ json common_chat_msg::to_json_oaicompat(bool concat_typed_text) const { {"type", "function"}, {"function", { {"name", tool_call.name}, - {"arguments", tool_call.arguments}, + {"arguments", json::parse(tool_call.arguments)}, }}, }; if (!tool_call.id.empty()) { diff --git a/common/jinja/value.h b/common/jinja/value.h index 0425bda5e3..df3eeaf444 100644 --- a/common/jinja/value.h +++ b/common/jinja/value.h @@ -502,12 +502,21 @@ struct value_object_t : public value_t { virtual bool is_immutable() const override { return false; } virtual const std::vector> & as_ordered_object() const override { return val_obj; } virtual string as_string() const override { + // Use JSON format for object string representation to ensure compatibility + // when concatenated in templates (e.g., '{"name": ' + arguments + '}') std::ostringstream ss; ss << "{"; for (size_t i = 0; i < val_obj.size(); i++) { if (i > 0) ss << ", "; auto & [key, val] = val_obj.at(i); - ss << value_to_string_repr(key) << ": " << value_to_string_repr(val); + // Use double quotes for keys (JSON format) + ss << "\"" << key->as_string().str() << "\": "; + if (is_val(val)) { + // Strings need to be quoted in JSON + ss << "\"" << val->as_string().str() << "\""; + } else { + ss << val->as_string().str(); + } } ss << "}"; return ss.str(); diff --git a/tests/test-chat.cpp b/tests/test-chat.cpp index 38798ec9d7..304370f2c1 100644 --- a/tests/test-chat.cpp +++ b/tests/test-chat.cpp @@ -973,7 +973,6 @@ static void test_msgs_oaicompat_json_conversion() { common_chat_msgs_to_json_oaicompat({ message_user_parts }).dump(2)); // Note: content is "" instead of null due to workaround for templates that render null as "None" - // Arguments are serialized as string for OAI compatibility assert_equals(std::string("[\n" " {\n" " \"role\": \"assistant\",\n" @@ -983,7 +982,9 @@ static void test_msgs_oaicompat_json_conversion() { " \"type\": \"function\",\n" " \"function\": {\n" " \"name\": \"python\",\n" - " \"arguments\": \"{\\\"code\\\":\\\"print('hey')\\\"}\"\n" + " \"arguments\": {\n" + " \"code\": \"print('hey')\"\n" + " }\n" " }\n" " }\n" " ]\n"