Skip to content

Commit f1c0521

Browse files
committed
tool calling xml issue fixed, but tool type is still having issues
1 parent 45fac2a commit f1c0521

File tree

13 files changed

+4109
-37
lines changed

13 files changed

+4109
-37
lines changed

.github/chatmodes/custom_models.chatmode.md

Whitespace-only changes.

common/chat.cpp

Lines changed: 210 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1331,62 +1331,235 @@ static void common_chat_parse_gpt_oss(common_chat_msg_parser & builder) {
13311331

13321332
static common_chat_params common_chat_params_init_glm_4_5(const common_chat_template & tmpl, const struct templates_params & inputs) {
13331333
common_chat_params data;
1334-
data.prompt = apply(tmpl, inputs);
1334+
1335+
// Bypass minja's tool processing entirely
1336+
minja::chat_template_inputs tmpl_inputs;
1337+
tmpl_inputs.messages = inputs.messages;
1338+
tmpl_inputs.tools = inputs.tools.empty() ? json() : inputs.tools;
1339+
tmpl_inputs.add_generation_prompt = inputs.add_generation_prompt;
1340+
tmpl_inputs.extra_context = inputs.extra_context;
1341+
tmpl_inputs.now = std::chrono::system_clock::now();
1342+
1343+
// Force XML mode through context
1344+
tmpl_inputs.extra_context["xml_tool_format"] = true;
1345+
tmpl_inputs.extra_context["native_tool_support"] = true;
1346+
1347+
minja::chat_template_options opts;
1348+
opts.apply_polyfills = false; // Hard disable all polyfills
1349+
opts.use_bos_token = inputs.add_bos;
1350+
opts.use_eos_token = inputs.add_eos;
1351+
1352+
// Single apply call
1353+
auto result = tmpl.apply(tmpl_inputs, opts);
1354+
1355+
// Manual BOS/EOS handling (since you disabled automatic handling)
1356+
if (inputs.add_bos && string_starts_with(result, tmpl.bos_token())) {
1357+
result = result.substr(tmpl.bos_token().size());
1358+
}
1359+
if (inputs.add_eos && string_ends_with(result, tmpl.eos_token())) {
1360+
result = result.substr(0, result.size() - tmpl.eos_token().size());
1361+
}
1362+
1363+
data.prompt = result;
13351364
data.format = COMMON_CHAT_FORMAT_GLM_4_5;
1365+
data.preserved_tokens = {
1366+
"<|system|>", "<|assistant|>", "<|observation|>",
1367+
"<tool_call>", "</tool_call>", "<arg_key>", "</arg_key>",
1368+
"<arg_value>", "</arg_value>", "<think>", "</think>",
1369+
"<tool_response>", "</tool_response>",
1370+
};
1371+
1372+
// Store tools schema for type-aware parsing
1373+
data.tools_schema = inputs.tools;
1374+
13361375
return data;
13371376
}
13381377

1339-
static void common_chat_parse_glm_4_5(common_chat_msg_parser & builder) {
1340-
builder.consume_spaces();
1341-
builder.try_parse_reasoning("<think>", "</think>");
1342-
if (!builder.syntax().parse_tool_calls) {
1343-
builder.add_content(builder.consume_rest());
1344-
return;
1345-
}
1378+
static void debug_print_raw_input(const std::string& input) {
1379+
LOG_INF("=== GLM-4.5 RAW INPUT ===\n");
1380+
for (size_t i = 0; i < input.size(); ++i) {
1381+
char ch = input[i];
1382+
if (ch == '\n') LOG_INF("\\n");
1383+
else if (ch == '\t') LOG_INF("\\t");
1384+
else if (ch == '\r') LOG_INF("\\r");
1385+
else if (std::isspace(ch)) LOG_INF("·"); // visible space
1386+
else LOG_INF("%c", ch);
1387+
}
1388+
LOG_INF("\n=== END RAW INPUT ===\n");
1389+
}
13461390

1347-
// GLM 4.5 uses format: <tool_call>function_name\n<arg_key>key</arg_key>\n<arg_value>value</arg_value>\n</tool_call>
1348-
static const common_regex tool_call_start("<tool_call>([^\n<]+)");
1349-
static const common_regex arg_key_regex("<arg_key>([^<]+)</arg_key>");
1350-
static const common_regex arg_value_regex("<arg_value>([\\s\\S]*?)</arg_value>");
1351-
static const common_regex tool_call_end("</tool_call>");
1391+
static void debug_print_parse_position(const std::string& input, size_t pos, const char* context) {
1392+
size_t start = (pos < 50) ? 0 : pos - 50;
1393+
size_t end = std::min(input.size(), pos + 50);
1394+
LOG_INF("=== %s at position %zu ===\n", context, pos);
1395+
for (size_t i = start; i < end; ++i) {
1396+
if (i == pos) LOG_INF(">>>");
1397+
char ch = input[i];
1398+
if (ch == '\n') LOG_INF("\\n");
1399+
else if (ch == '\t') LOG_INF("\\t");
1400+
else LOG_INF("%c", ch);
1401+
if (i == pos) LOG_INF("<<<");
1402+
}
1403+
LOG_INF("\n=== END POSITION ===\n");
1404+
}
1405+
1406+
static void common_chat_parse_glm_4_5(common_chat_msg_parser & builder) {
1407+
debug_print_raw_input(builder.input());
1408+
1409+
// Helper function to get expected type from tool schema
1410+
auto get_expected_type = [&](const std::string& tool_name, const std::string& param_name) -> std::string {
1411+
// Access tools schema from builder syntax
1412+
const auto& tools_schema = builder.syntax().tools_schema;
1413+
if (tools_schema.is_array()) {
1414+
for (const auto& tool : tools_schema) {
1415+
if (tool.contains("function") && tool["function"]["name"] == tool_name) {
1416+
auto params = tool["function"]["parameters"];
1417+
if (params.contains("properties") && params["properties"].contains(param_name)) {
1418+
return params["properties"][param_name].value("type", "string");
1419+
}
1420+
}
1421+
}
1422+
}
1423+
return "string"; // Default fallback
1424+
};
13521425

1353-
while (auto res = builder.try_find_regex(tool_call_start)) {
1354-
// Move to the start of the tool call and consume it
1355-
builder.move_to(res->groups[0].begin);
1356-
builder.consume_regex(tool_call_start);
1426+
auto handle_tool_call_end = [&] (common_chat_msg_parser & builder, auto end_pos) {
1427+
builder.move_to(end_pos);
1428+
builder.consume_literal("</tool_call>");
13571429

1358-
std::string function_name = builder.str(res->groups[1]);
1359-
json arguments = json::object();
1430+
size_t obs_pos = builder.input().find("<|observation|>", builder.pos());
1431+
if (obs_pos != std::string::npos) {
1432+
if (obs_pos > builder.pos()) {
1433+
std::string content = builder.input().substr(builder.pos(), obs_pos - builder.pos());
1434+
builder.add_content(content);
1435+
}
1436+
1437+
builder.move_to(obs_pos);
1438+
builder.consume_literal("<|observation|>");
1439+
} else {
1440+
std::string remaining = builder.consume_rest();
1441+
if (!remaining.empty()) builder.add_content(remaining);
1442+
}
1443+
};
1444+
1445+
builder.consume_spaces();
1446+
1447+
builder.try_parse_reasoning("<think>", "</think>");
1448+
1449+
size_t curr_pos = builder.pos();
1450+
while (builder.input().find("<tool_call>", builder.pos()) != std::string::npos) {
1451+
size_t tool_call_start = builder.input().find("<tool_call>", builder.pos());
1452+
if (tool_call_start > builder.pos()) {
1453+
std::string content = builder.input().substr(builder.pos(), tool_call_start - builder.pos());
1454+
builder.add_content(content);
1455+
}
13601456

1457+
size_t tool_call_end = builder.input().find("</tool_call>", tool_call_start);
1458+
if (tool_call_end == std::string::npos) return;
1459+
1460+
builder.move_to(tool_call_start);
1461+
builder.consume_literal("<tool_call>");
13611462
builder.consume_spaces();
1362-
1363-
// Parse all arg_key/arg_value pairs
1364-
while (auto key_res = builder.try_consume_regex(arg_key_regex)) {
1365-
std::string key = builder.str(key_res->groups[1]);
1366-
builder.consume_spaces();
1463+
1464+
size_t arg_key_start = builder.input().find("<arg_key>", tool_call_start);
1465+
if (arg_key_start == std::string::npos) {
1466+
std::string function_content = builder.input().substr(builder.pos(), tool_call_end - builder.pos());
1467+
std::string function_name = string_strip(function_content);
1468+
1469+
if (!builder.add_tool_call(function_name, "", "{}")) {
1470+
LOG_INF("%s: failed to add tool call\n", __func__);
1471+
}
1472+
1473+
handle_tool_call_end(builder, tool_call_end);
1474+
1475+
} else {
1476+
std::string function_content = builder.input().substr(builder.pos(), arg_key_start - builder.pos());
1477+
std::string function_name = string_strip(function_content);
1478+
1479+
json args_json = json::object();
1480+
builder.move_to(arg_key_start);
13671481

1368-
if (auto value_res = builder.try_consume_regex(arg_value_regex)) {
1369-
std::string value = builder.str(value_res->groups[1]);
1370-
arguments[key] = value;
1482+
while (builder.pos() < tool_call_end) {
1483+
if (!builder.try_consume_literal("<arg_key>")) {
1484+
builder.consume_spaces();
1485+
if (!builder.try_consume_literal("<arg_key>")) {
1486+
break;
1487+
}
1488+
}
1489+
1490+
auto key_close = builder.try_find_literal("</arg_key>");
1491+
if (!key_close || key_close->groups[0].end > tool_call_end) {
1492+
throw common_chat_msg_partial_exception("incomplete tool call");
1493+
return;
1494+
}
1495+
1496+
std::string key = string_strip(key_close->prelude);
1497+
13711498
builder.consume_spaces();
1499+
1500+
if (!builder.try_consume_literal("<arg_value>")) {
1501+
throw common_chat_msg_partial_exception("incomplete tool call");
1502+
return;
1503+
}
1504+
1505+
auto value_close = builder.try_find_literal("</arg_value>");
1506+
if (!value_close || value_close->groups[0].end > tool_call_end) {
1507+
throw common_chat_msg_partial_exception("incomplete tool call");
1508+
return;
1509+
}
1510+
1511+
std::string value = string_strip(value_close->prelude);
1512+
1513+
// Schema-aware type conversion
1514+
std::string expected_type = get_expected_type(function_name, key);
1515+
json parsed_value;
1516+
1517+
if (expected_type == "integer" || expected_type == "number") {
1518+
try {
1519+
parsed_value = std::stod(value); // or std::stoi for integers
1520+
} catch (...) {
1521+
parsed_value = value; // Fallback to string
1522+
}
1523+
} else if (expected_type == "boolean") {
1524+
parsed_value = (value == "true");
1525+
} else if (expected_type == "array" || expected_type == "object") {
1526+
try {
1527+
parsed_value = json::parse(value);
1528+
} catch (...) {
1529+
parsed_value = value;
1530+
}
1531+
} else {
1532+
// Default to string
1533+
parsed_value = value;
1534+
}
1535+
1536+
args_json[key] = parsed_value;
1537+
1538+
builder.consume_spaces();
1539+
}
1540+
1541+
if (!builder.add_tool_call(function_name, "", args_json.dump())) {
1542+
LOG_INF("%s: failed to add tool call with arguments\n", __func__);
13721543
} else {
1373-
throw common_chat_msg_partial_exception("Expected <arg_value> after <arg_key>");
1544+
LOG_INF("%s: successfully added tool call with arguments\n", __func__);
13741545
}
1546+
1547+
handle_tool_call_end(builder, tool_call_end);
13751548
}
1376-
1377-
// Consume closing tag
1378-
builder.consume_regex(tool_call_end);
1379-
builder.consume_spaces();
1380-
1381-
// Add the parsed tool call
1382-
if (!builder.add_tool_call(function_name, "", arguments.dump())) {
1383-
throw common_chat_msg_partial_exception("Failed to add GLM tool call");
1549+
1550+
if (curr_pos == builder.pos()) {
1551+
// No progress made, avoid infinite loop
1552+
LOG_INF("%s: no progress in parsing, stopping to avoid infinite loop\n", __func__);
1553+
break;
13841554
}
13851555
}
13861556

1387-
builder.add_content(builder.consume_rest());
1557+
if (builder.pos() < builder.input().size()) {
1558+
builder.add_content(builder.consume_rest());
1559+
}
13881560
}
13891561

1562+
13901563
static common_chat_params common_chat_params_init_firefunction_v2(const common_chat_template & tmpl, const struct templates_params & inputs) {
13911564
LOG_DBG("%s\n", __func__);
13921565
common_chat_params data;

common/chat.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#pragma once
44

55
#include "common.h"
6+
#include <nlohmann/json.hpp>
67
#include <functional>
78
#include <chrono>
89
#include <string>
@@ -142,6 +143,7 @@ struct common_chat_params {
142143
std::vector<common_grammar_trigger> grammar_triggers;
143144
std::vector<std::string> preserved_tokens;
144145
std::vector<std::string> additional_stops;
146+
nlohmann::ordered_json tools_schema = nlohmann::ordered_json(); // Schema for tools to pass to parser
145147
};
146148

147149
struct common_chat_syntax {
@@ -151,6 +153,7 @@ struct common_chat_syntax {
151153
bool reasoning_in_content = false;
152154
bool thinking_forced_open = false;
153155
bool parse_tool_calls = true;
156+
nlohmann::ordered_json tools_schema = nlohmann::ordered_json(); // Schema for tools to enable type-aware parsing
154157
};
155158

156159
// Check if the template supplied via "--chat-template" is supported or not. Returns true if it's valid

doc.txt

Lines changed: 102 additions & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)