add gigachat v3 parser in PEG format
This commit is contained in:
parent
d4f723b56e
commit
37fa3365eb
|
|
@ -879,32 +879,6 @@ static void common_chat_parse_deepseek_v3_1(common_chat_msg_parser & builder) {
|
|||
}
|
||||
}
|
||||
|
||||
static void common_chat_parse_gigachat_v3(common_chat_msg_parser & builder) {
|
||||
if (!builder.syntax().parse_tool_calls) {
|
||||
builder.add_content(builder.consume_rest());
|
||||
return;
|
||||
}
|
||||
|
||||
// Regex to capture function name from JSON after "function call<|role_sep|\n"
|
||||
static const common_regex function_regex(
|
||||
R"(<\|message_sep\|>\n\nfunction call<\|role_sep\|>\n\s*\{\s*\"name\"\s*:\s*\"([^\"]+)\"\s*,\s*\"arguments\"\s*:)"
|
||||
);
|
||||
|
||||
// Closing token of a tool call
|
||||
static const common_regex close_regex(
|
||||
R"(\}\s*)"
|
||||
);
|
||||
|
||||
parse_json_tool_calls(
|
||||
builder,
|
||||
/* block_open = */ std::nullopt,
|
||||
/* function_regex_start_only = */ std::nullopt,
|
||||
/* function_regex = */ function_regex,
|
||||
close_regex,
|
||||
/* block_close = */ std::nullopt
|
||||
);
|
||||
}
|
||||
|
||||
static void common_chat_parse_minimax_m2(common_chat_msg_parser & builder) {
|
||||
static const xml_tool_call_format form {
|
||||
/* form.scope_start = */ "<minimax:tool_call>",
|
||||
|
|
@ -1505,9 +1479,6 @@ static void common_chat_parse(common_chat_msg_parser & builder) {
|
|||
case COMMON_CHAT_FORMAT_XIAOMI_MIMO:
|
||||
common_chat_parse_xiaomi_mimo(builder);
|
||||
break;
|
||||
case COMMON_CHAT_FORMAT_GIGACHAT_V3:
|
||||
common_chat_parse_gigachat_v3(builder);
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error(std::string("Unsupported format: ") + common_chat_format_name(builder.syntax().format));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -672,7 +672,6 @@ const char * common_chat_format_name(common_chat_format format) {
|
|||
case COMMON_CHAT_FORMAT_PEG_SIMPLE: return "peg-simple";
|
||||
case COMMON_CHAT_FORMAT_PEG_NATIVE: return "peg-native";
|
||||
case COMMON_CHAT_FORMAT_PEG_CONSTRUCTED: return "peg-constructed";
|
||||
case COMMON_CHAT_FORMAT_GIGACHAT_V3: return "GigaChat V3";
|
||||
default:
|
||||
throw std::runtime_error("Unknown chat format");
|
||||
}
|
||||
|
|
@ -1761,55 +1760,68 @@ static common_chat_params common_chat_params_init_gigachat_v3(
|
|||
|
||||
auto prompt = apply(tmpl, inputs);
|
||||
data.prompt = prompt;
|
||||
data.format = COMMON_CHAT_FORMAT_GIGACHAT_V3;
|
||||
data.format = COMMON_CHAT_FORMAT_PEG_NATIVE;
|
||||
|
||||
data.preserved_tokens = {
|
||||
"<|message_sep|>\n\n",
|
||||
"<|role_sep|>\n",
|
||||
};
|
||||
|
||||
if (inputs.tools.is_array() && !inputs.tools.empty()) {
|
||||
data.grammar_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED && inputs.json_schema.is_null();
|
||||
auto has_tools = inputs.tools.is_array() && !inputs.tools.empty();
|
||||
auto include_grammar = true;
|
||||
auto tool_call_start_prefix = "<|message_sep|>\n\nfunction call<|role_sep|>\n";
|
||||
|
||||
data.grammar = build_grammar([&](const common_grammar_builder & builder) {
|
||||
std::vector<std::string> rules;
|
||||
|
||||
|
||||
foreach_function(inputs.tools, [&](const json & tool) {
|
||||
auto parser = build_chat_peg_native_parser([&](common_chat_peg_native_builder & p) {
|
||||
if (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE) {
|
||||
// Build a choice of all available tools
|
||||
auto tool_choice = p.choice();
|
||||
for (const auto & tool : inputs.tools) {
|
||||
const auto & function = tool.at("function");
|
||||
std::string name = function.at("name");
|
||||
auto parameters = function.at("parameters");
|
||||
builder.resolve_refs(parameters);
|
||||
const auto & schema = function.at("parameters");
|
||||
|
||||
// JSON schema for this tool
|
||||
json schema = {
|
||||
{"type", "object"},
|
||||
{"properties", {
|
||||
{"name", {{"type", "string"}, {"const", name}}},
|
||||
{"arguments", parameters}
|
||||
}},
|
||||
{"required", json::array({"name", "arguments"})}
|
||||
};
|
||||
auto tool_name = p.json_member("name", "\"" + p.tool_name(p.literal(name)) + "\"");
|
||||
auto tool_args = p.json_member("arguments", p.tool_args(p.schema(p.json(), "tool-" + name + "-schema", schema)));
|
||||
|
||||
// Add a rule for this tool
|
||||
rules.push_back(
|
||||
R"("<|message_sep|>\n\nfunction call<|role_sep|>\n")" +
|
||||
builder.add_schema(name + "-call", schema)
|
||||
);
|
||||
});
|
||||
|
||||
builder.add_rule("root",
|
||||
"(" + string_join(rules, " | ") + ")" +
|
||||
(inputs.parallel_tool_calls ? "*" : "")
|
||||
);
|
||||
|
||||
data.grammar_triggers.push_back({
|
||||
COMMON_GRAMMAR_TRIGGER_TYPE_WORD,
|
||||
"<|message_sep|>\n\nfunction call<|role_sep|>\n"
|
||||
auto tool_open = p.tool_open(p.literal("{") << tool_name);
|
||||
|
||||
tool_choice |= p.rule("tool-" + name, tool_open << "," << tool_args << "}");
|
||||
}
|
||||
|
||||
// Define the tool call structure
|
||||
auto min_calls = inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED ? 1 : 0;
|
||||
auto max_calls = 1; // parallel toolcalls are not supported
|
||||
auto tool_call = p.rule("tool-call", p.literal(tool_call_start_prefix) + tool_choice);
|
||||
auto tool_calls = p.trigger_rule("tool-call-root", p.repeat(tool_call, /* min = */ min_calls, /* max = */ max_calls));
|
||||
|
||||
return p.content(p.until("<|message_sep|>\n\n")) << tool_calls;
|
||||
}
|
||||
|
||||
// Content only parser
|
||||
include_grammar = false;
|
||||
return p.content(p.rest());
|
||||
|
||||
});
|
||||
|
||||
data.parser = parser.save();
|
||||
|
||||
if (include_grammar) {
|
||||
data.grammar_lazy = has_tools && inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_AUTO;
|
||||
|
||||
data.grammar = build_grammar([&](const common_grammar_builder & builder) {
|
||||
foreach_function(inputs.tools, [&](const json & tool) {
|
||||
const auto & function = tool.at("function");
|
||||
auto schema = function.at("parameters");
|
||||
builder.resolve_refs(schema);
|
||||
});
|
||||
parser.build_grammar(builder, data.grammar_lazy);
|
||||
});
|
||||
}
|
||||
|
||||
data.grammar_triggers = {
|
||||
{COMMON_GRAMMAR_TRIGGER_TYPE_WORD, tool_call_start_prefix}
|
||||
};
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -124,7 +124,6 @@ enum common_chat_format {
|
|||
COMMON_CHAT_FORMAT_QWEN3_CODER_XML,
|
||||
COMMON_CHAT_FORMAT_APRIEL_1_5,
|
||||
COMMON_CHAT_FORMAT_XIAOMI_MIMO,
|
||||
COMMON_CHAT_FORMAT_GIGACHAT_V3,
|
||||
|
||||
// These are intended to be parsed by the PEG parser
|
||||
COMMON_CHAT_FORMAT_PEG_SIMPLE,
|
||||
|
|
|
|||
|
|
@ -3499,74 +3499,6 @@ Hey there!<|im_end|>
|
|||
auto grammar = build_grammar(params.grammar);
|
||||
GGML_ASSERT(grammar && "Failed to build Qwen3-Coder grammar with union types");
|
||||
}
|
||||
|
||||
{
|
||||
auto tmpls = read_templates("models/templates/GigaChat3-10B-A1.8B.jinja");
|
||||
std::vector<std::string> end_tokens{ "</s>", "<|message_sep|>\n\n" };
|
||||
|
||||
assert_equals(COMMON_CHAT_FORMAT_GIGACHAT_V3, common_chat_templates_apply(tmpls.get(), inputs_no_tools).format);
|
||||
assert_equals(COMMON_CHAT_FORMAT_GIGACHAT_V3, common_chat_templates_apply(tmpls.get(), inputs_tools).format);
|
||||
|
||||
// Test parsing regular content
|
||||
assert_msg_equals(message_assist,
|
||||
common_chat_parse(
|
||||
"Hello, world!\nWhat's up?",
|
||||
/* is_partial= */ false,
|
||||
{COMMON_CHAT_FORMAT_GIGACHAT_V3}));
|
||||
|
||||
// Test parsing tool calls
|
||||
assert_msg_equals(message_assist_call,
|
||||
common_chat_parse(
|
||||
"<|message_sep|>\n\nfunction call<|role_sep|>\n{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}",
|
||||
/* is_partial= */ false,
|
||||
{COMMON_CHAT_FORMAT_GIGACHAT_V3}));
|
||||
|
||||
// Test tool calls with extra content
|
||||
assert_msg_equals(message_assist_call_content,
|
||||
common_chat_parse(
|
||||
"Hello, world!\nWhat's up?<|message_sep|>\n\nfunction call<|role_sep|>\n{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}",
|
||||
/* is_partial= */ false,
|
||||
{COMMON_CHAT_FORMAT_GIGACHAT_V3}
|
||||
));
|
||||
|
||||
// Test streaming
|
||||
test_parser_with_streaming(message_assist_call_withopt,
|
||||
"<|message_sep|>\n\nfunction call<|role_sep|>\n{\"name\": \"special_function_with_opt\", \"arguments\": {\"arg1\": 1, \"arg2\": 2}}",
|
||||
[&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {
|
||||
/* .format = */ COMMON_CHAT_FORMAT_GIGACHAT_V3,
|
||||
/* .reasoning_format = */ COMMON_REASONING_FORMAT_NONE
|
||||
}); });
|
||||
|
||||
// Test template generation for regular content
|
||||
test_templates(tmpls.get(), end_tokens, message_assist, tools,
|
||||
"Hello, world!\nWhat's up?",
|
||||
/* expect_grammar_triggered= */ false);
|
||||
|
||||
// Test template generation for tool calls
|
||||
test_templates(tmpls.get(), end_tokens, message_assist_call, tools,
|
||||
"<|message_sep|>\n\nfunction call<|role_sep|>\n{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}",
|
||||
/* expect_grammar_triggered= */ true,
|
||||
/* test_grammar_if_triggered= */ true,
|
||||
/* common_reasoning_format= */ COMMON_REASONING_FORMAT_NONE,
|
||||
/* ignore_whitespace_differences= */ true
|
||||
);
|
||||
|
||||
// Test template generation for tools with optional parameters
|
||||
test_templates(tmpls.get(), end_tokens, message_assist_call_noopt, tools,
|
||||
"<|message_sep|>\n\nfunction call<|role_sep|>\n{\"name\": \"special_function_with_opt\", \"arguments\": {\"arg1\": 1}}",
|
||||
/* expect_grammar_triggered= */ true,
|
||||
/* test_grammar_if_triggered= */ true,
|
||||
/* common_reasoning_format= */ COMMON_REASONING_FORMAT_NONE,
|
||||
/* ignore_whitespace_differences= */ true
|
||||
);
|
||||
test_templates(tmpls.get(), end_tokens, message_assist_call_withopt, tools,
|
||||
"<|message_sep|>\n\nfunction call<|role_sep|>\n{\"name\": \"special_function_with_opt\", \"arguments\": {\"arg1\": 1, \"arg2\": 2}}",
|
||||
/* expect_grammar_triggered= */ true,
|
||||
/* test_grammar_if_triggered= */ true,
|
||||
/* common_reasoning_format= */ COMMON_REASONING_FORMAT_NONE,
|
||||
/* ignore_whitespace_differences= */ true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_template_output_peg_parsers() {
|
||||
|
|
@ -3812,6 +3744,34 @@ static void test_template_output_peg_parsers() {
|
|||
t.expect.content = R"({"amount": 123.45, "date": "2025-12-03"})";
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
// GigaChat V3
|
||||
auto tmpls = read_templates("models/templates/GigaChat3-10B-A1.8B.jinja");
|
||||
|
||||
// Test basic message
|
||||
test_peg_parser(tmpls.get(), [&](auto & t) {
|
||||
t.input = "Hello, world!\nWhat's up?";
|
||||
t.expect = message_assist;
|
||||
});
|
||||
|
||||
// Test tool call
|
||||
test_peg_parser(tmpls.get(), [&](auto & t) {
|
||||
t.input = "<|message_sep|>\n\nfunction call<|role_sep|>\n{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}";
|
||||
t.params.tools = {special_function_tool};
|
||||
|
||||
t.expect = message_assist_call;
|
||||
});
|
||||
|
||||
// Test tool call with content before
|
||||
test_peg_parser(tmpls.get(), [&](auto & t) {
|
||||
t.input = "Hello, world!\nWhat's up?"
|
||||
"<|message_sep|>\n\nfunction call<|role_sep|>\n{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}";
|
||||
t.params.tools = {special_function_tool};
|
||||
|
||||
t.expect = message_assist_call_content;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static void test_msg_diffs_compute() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue