Repository: ggerganov/llama.cpp
File: common/chat-parser.cpp (C++)

Code before:
        builder.consume_spaces();
    }

    // Consume any remaining content after all tool calls
    auto remaining = builder.consume_rest();
    if (!string_strip(remaining).empty()) {
        builder.add_content(remaining);
    }
}

static void common_chat_parse_seed_oss(common_chat_msg_parser & builder) {
    static const xml_tool_call_format form {
        /* form.scope_start = */ "<seed:tool_call>",
        /* form.tool_start  = */ "<function=",
        /* form.tool_sep    = */ ">",
        /* form.key_start   = */ "<parameter=",
        /* form.key_val_sep = */ ">",
        /* form.val_end     = */ "</parameter>",
        /* form.tool_end    = */ "</function>",
        /* form.scope_end   = */ "</seed:tool_call>",
    };
    builder.consume_reasoning_with_xml_tool_calls(form, "<seed:think>", "</seed:think>");
}

static void common_chat_parse_solar_open(common_chat_msg_parser & builder) {
    builder.try_parse_reasoning("
", "
assistant");

    // TODO: Tool calling

    builder.add_content(builder.consume_rest());
}

static void common_chat_parse_content_only(common_chat_msg_parser & builder) {
    builder.try_parse_reasoning("<think>", "</think>");
    builder.add_content(builder.consume_rest());
}

static void common_chat_parse(common_chat_msg_parser & builder) {
    LOG_DBG("Parsing input with format %s: %s\n", common_chat_format_name(builder.syntax().format), builder.input().c_str());

    switch (builder.syntax().format) {
        case COMMON_CHAT_FORMAT_CONTENT_ONLY:
            common_chat_parse_content_only(builder);
            break;
        case COMMON_CHAT_FORMAT_GENERIC:
            common_chat_parse_generic(builder);
            break;
        case COMMON_CHAT_FORMAT_MISTRAL_NEMO:
            common_chat_parse_mistral_nemo(builder);
            break;
        case COMMON_CHAT_FORMAT_MAGISTRAL:

Review comment (bug):
This is actually incorrect.
The model seems to have the weird behavior where it puts reasoning content and normal content on separate assistant blocks. Look at the template:

```jinja
<html>
<body>
{%- elif message.role == 'assistant' -%}
{%- if think_render_option == "all" -%}
{%- if message.reasoning -%}
{{- "<\|begin\|>assistant<\|think\|>" + message.reasoning + "<\|end\|>" }}
{%- endif -%}
{%- elif think_render_option == "lastthink" -%}
{%- if message.reasoning and loop.index0 > last_user_idx.value -%}
{{- "<\|begin\|>assistant<\|think\|>" + message.reasoning + "<\|end\|>" }}
{%- endif -%}
{%- endif -%}
 {%- if message.tool_calls -%}
{{- "<\|begin\|>assistant<\|tool_calls\|>" }}
{%- for tool_call in message.tool_calls -%}
{{- "<\|tool_call:begin\|>" + tool_call.id +"<\|tool_call:name\|>" + tool_call.function.name + "<\|tool_call:args\|>" }}
{{- render_tool_arguments(tool_call.function.arguments) }}
{{- "<\|tool_call:end\|>" }}
{%- endfor -%}
{{- "<\|calls\|>" }}
{%- else -%}
{{- "<\|begin\|>assistant<\|content\|>" + message.content + "<\|end\|>" }}
{%- endif -%}
{%- endif -%}
```

So the proper end for reasoning would actually seem to be:

```
builder.try_parse_reasoning("
", "
\n
assistant
");
```

Code after:
        builder.consume_spaces();
    }

    // Consume any remaining content after all tool calls
    auto remaining = builder.consume_rest();
    if (!string_strip(remaining).empty()) {
        builder.add_content(remaining);
    }
}

static void common_chat_parse_seed_oss(common_chat_msg_parser & builder) {
    static const xml_tool_call_format form {
        /* form.scope_start = */ "<seed:tool_call>",
        /* form.tool_start  = */ "<function=",
        /* form.tool_sep    = */ ">",
        /* form.key_start   = */ "<parameter=",
        /* form.key_val_sep = */ ">",
        /* form.val_end     = */ "</parameter>",
        /* form.tool_end    = */ "</function>",
        /* form.scope_end   = */ "</seed:tool_call>",
    };
    builder.consume_reasoning_with_xml_tool_calls(form, "<seed:think>", "</seed:think>");
}

static void common_chat_parse_solar_open(common_chat_msg_parser & builder) {
    builder.try_parse_reasoning("
", "

assistant
");

    // TODO: Tool calling

    builder.add_content(builder.consume_rest());
}

static void common_chat_parse_content_only(common_chat_msg_parser & builder) {
    builder.try_parse_reasoning("<think>", "</think>");
    builder.add_content(builder.consume_rest());
}

static void common_chat_parse(common_chat_msg_parser & builder) {
    LOG_DBG("Parsing input with format %s: %s\n", common_chat_format_name(builder.syntax().format), builder.input().c_str());

    switch (builder.syntax().format) {
        case COMMON_CHAT_FORMAT_CONTENT_ONLY:
            common_chat_parse_content_only(builder);
            break;
        case COMMON_CHAT_FORMAT_GENERIC:
            common_chat_parse_generic(builder);
            break;
        case COMMON_CHAT_FORMAT_MISTRAL_NEMO:
            common_chat_parse_mistral_nemo(builder);
            break;
        case COMMON_CHAT_FORMAT_MAGISTRAL: