Skip to content

Ministral 3 support: error "Only user, assistant and tool roles are supported, got system" #5034

Closed as not planned

Description

@espadrine

Description

I wish to use opencode with Ministral 3 14B which was recently released. However, opencode raises an error immediately:

Only user, assistant and tool roles are supported, got system

To understand it better, here is first my setup, and how to reproduce the error, which I expect will occur even for the Ministral 3 3B, as we will see later.

I run the server using weights downloaded from unsloth:

huggingface-cli download unsloth/Ministral-3-14B-Reasoning-2512-GGUF Ministral-3-14B-Reasoning-2512-Q5_K_M.gguf
docker run --gpus all -v /data/ml/models/gguf:/models -p 11432:8080 ghcr.io/ggml-org/llama.cpp:full-cuda -s --host 0.0.0.0 -m "/models/Ministral-3-14B-Reasoning-2512-Q5_K_M.gguf" --ctx-size 0 -ub 2048 -b 2048 --n-gpu-layers 99 -fa on -c 65536

I then run opencode with the following configuration, as per the documentation:

{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "llama.cpp": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "llama-server (local)",
      "options": {
        "baseURL": "http://127.0.0.1:11432/v1"
      },
      "models": {
        "ministral-3-14b": {
          "name": "Ministral 3 14B (local)"
        }
      }
    }
  }
}

Then I request anything, and get the error pasted at the top.

The reason I get this error is that llama.cpp relies on the Jinja template inset in the GGUF, which it copied from the original Ministral repository. It loops over loop_messages, expecting not to have system messages. It only expects a system message on the first message.

However, that clearly is not the case with opencode, which can seemingly have up to 3 system messages: https://github.com/sst/opencode/blob/ee4437ff32fc2acbd2220060fc980a096730bcee/packages/opencode/src/session/prompt.ts#L651-L660

For opencode to support Ministral, it needs a single system prompt with the current Jinja template, concatenating them.

OpenCode version

1.0.129

Steps to reproduce

I run the server using weights downloaded from unsloth:

huggingface-cli download unsloth/Ministral-3-14B-Reasoning-2512-GGUF Ministral-3-14B-Reasoning-2512-Q5_K_M.gguf
docker run --gpus all -v /data/ml/models/gguf:/models -p 11432:8080 ghcr.io/ggml-org/llama.cpp:full-cuda -s --host 0.0.0.0 -m "/models/Ministral-3-14B-Reasoning-2512-Q5_K_M.gguf" --ctx-size 0 -ub 2048 -b 2048 --n-gpu-layers 99 -fa on -c 65536

I then run opencode with the following configuration, as per the documentation:

{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "llama.cpp": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "llama-server (local)",
      "options": {
        "baseURL": "http://127.0.0.1:11432/v1"
      },
      "models": {
        "ministral-3-14b": {
          "name": "Ministral 3 14B (local)"
        }
      }
    }
  }
}

Then I enter a message, and I see the error:

Only user, assistant and tool roles are supported, got system

Screenshot and/or share link

Image

Operating System

Ubuntu 24.04.2 LTS

Terminal

Alacritty

Activity

  1. added
    opentuiThis relates to changes in v1.0, now that opencode uses opentui
    on Dec 4, 2025
  2. github-actions commented on Dec 4, 2025

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    All three issues stem from the same underlying problem: OpenCode generates up to 3 system messages (as referenced in prompt.ts:651-660), but many model providers and Jinja templates expect only a single system message, especially at the start of conversation.

    Feel free to ignore if your case differs, but these likely share the same root cause and solution.

  3. removed
    opentuiThis relates to changes in v1.0, now that opencode uses opentui
    on Dec 4, 2025
  4. self-assigned this
    on Dec 4, 2025
  5. arkamar commented on Dec 5, 2025

    @arkamar

    This one might be related to ggml-org/llama.cpp#17700 and ggml-org/llama.cpp#17713.

    EDIT: actually I am no longer able to reproduce this:

    The error message changes to

    After the optional system message, conversation roles must alternate user and assistant roles except for tool calls and results
    

    when I apply ggml-org/llama.cpp#17713.

  6. rekram1-node commented on Dec 7, 2025

    @rekram1-node
    Contributor

    I think this is an issue on llama.cpp side right

  7. arkamar commented on Dec 7, 2025

    @arkamar

    Well, I don't know. The problem is in default ministral jinja template https://huggingface.co/mistralai/Ministral-3-3B-Reasoning-2512/blob/f8aa436a36b5bf3c59e3c2e8b19368de9cf69914/chat_template.jinja, which basically requires alternating roles in messages. Therefore:

    {
        ...
        "messages": [
            {
                "role":"system",
                "content":"You are"
            },
            {
                "role":"system",
                "content":"Here is"
            },
            {
                "role":"user",
                "content":"test"
            }
        ],
        ...
    }

    and

    {
        ...
        "messages": [
            {
                "role":"system",
                "content":"You are"
            },
            {
                "role":"user",
                "content":"test 1"
            }
            {
                "role":"user",
                "content":"test 2"
            }
        ],
        ...
    }

    are both problematic. The first snippet is what opencode basically sends by default and the template throws an error: Only user, assistant and tool roles are supported, got system. at row 124, column 111... for it. The issue seems to be related to #2272.

  8. bamer commented on Dec 10, 2025

    @bamer

    Still not working, everything is fine on the llame.cpp side since they today but it's still not working with opencode :( and devstrall too ....Is there a quick bypass or fix ?

  9. kurnevsky commented on Dec 10, 2025

    @kurnevsky

    For quick fix you can merge all system prompts into one here, or modify mistral chat template and pass it via --chat-template. Would be good if opencode allowed to customize this.

  10. grigio commented on Dec 12, 2025

    @grigio

    A workaround is to use Mistral Vibe, but I hope opencode will support those models too

  11. rekram1-node commented on Dec 16, 2025

    @rekram1-node
    Contributor

    I think this should be fixed? The system prompts should be sent as merged (only exception currently is if u are using anthropic)

  12. haydonryan commented on Dec 30, 2025

    @haydonryan

    I'm getting this issue., with opencode 1.0.218 using devstral-2-small.

    $ llama-server --version
    ggml_vulkan: Found 1 Vulkan devices:
    ggml_vulkan: 0 = NVIDIA GeForce RTX 3090 (NVIDIA) | uma: 0 | fp16: 1 | bf16: 1 | warp size: 32 | shared memory: 49152 | int dot: 1 | matrix cores: NV_coopmat2
    version: 1087 (66d767f)
    built with GNU 15.2.1 for Linux x86_64
    
    llama-server \
        -hf unsloth/Devstral-Small-2-24B-Instruct-2512-GGUF:Q3_K_S \
        --jinja -ngl 99 --threads -1 --ctx-size 16384 \
        --temp 0.15 \
                    --no-mmap
    

    errors from llama-server

    srv operator(): got exception: {"error":{"code":500,"message":"After the optional system message, conversation roles must alternate user and assistant roles except for tool calls and results. at row 189, column 164:\n {%- if (message['role'] == 'user') != (ns.index % 2 == 0) %}\n
    {{- raise_exception('After the optional system message, conversation roles must alternate user and assistant roles except for tool calls and results.') }}\n ^\n
    {%- endif %}\n at row 189, column 13:\n {%- if (message['role'] == 'user') != (ns.index % 2 == 0) %}\n {{- raise_exception('After the optional system message, conversation roles must alternate user and assistant roles except for tool calls and results.') }}\n ^\n {%- endif %}\n at row 188, column 69:\n {%- if message.role == 'user' or (message.role == 'assistant' and (message.tool_calls is not defined or message.tool_calls is none or message.tool_calls | length == 0)) %}\n {%- if (message['role'] == 'user') != (ns.index % 2 == 0) %}\n
    ^\n {{- raise_exception('After the optional system message, conversation roles must alternate user and assistant roles except for tool calls and results.') }}\n at row 188, column 9:\n {%- if message.role == 'user' or (message.role == 'assistant' and (message.tool_calls is not defined or message.tool_calls is none or message.tool_calls | length == 0)) %}\n {%- if (message['role'] == 'user') != (ns.index % 2 == 0) %}\n ^\n
    {{- raise_exception('After the optional system message, conversation roles must alternate user and assistant roles except for tool calls and results.') }}\n at row 187, column 176:\n{%- for message in loop_messages %}\n {%- if message.role == 'user' or (message.role == 'assistant' and (message.tool_calls is not defined or message.tool_calls is none or message.tool_calls | length == 0)) %}\n ^\n
    {%- if (message['role'] == 'user') != (ns.index % 2 == 0) %}\n at row 187, column 5:\n{%- for message in loop_messages %}\n {%- if message.role == 'user' or (message.role == 'assistant' and (message.tool_calls is not defined or message.tool_calls is none or message.tool_calls | length == 0)) %}\n ^\n {%- if (message['role'] == 'user') != (ns.index % 2 == 0) %}\n at row 186, column 36:\n{%- set ns = namespace(index=0) %}\n{%- for message in loop_messages %}\n ^
    \n {%- if message.role == 'user' or (message.role == 'assistant' and (message.tool_calls is not defined or message.tool_calls is none or message.tool_calls | length == 0)) %}\n at row 186, column 1:\n{%- set ns = namespace(index=0) %}\n{%- for message in loop_messages %}\n^\n {%- if message.role == 'user' or (message.role == 'assistant' and (message.tool_calls is not defined or message.tool_calls is none or message.tool_calls | length == 0)) %}\n at row 1, column 30:\n{#- Unsloth template fixes #}\n
    ^\n{%- set yesterday_day = strftime_now("%d") %}\n","type":"server_error"}}

    (didn't put it in a code block as it tried to make it one really long line)

  13. lee-b commented on Jan 27, 2026

    @lee-b

    devstral 2 also affected :/

  14. lee-b commented on Jan 27, 2026

    @lee-b

    Well, I don't know. The problem is in default ministral jinja template

    I think it's desirable that all models would support non-alternating interactions, but that's probably an assumption too far on opencode's part, since the models are usually trained for back-and-forth dialog. Evidence would be that mistral vibe works, opencode does not.

    A quick fix might be to join contiguous responses from either party.

  15. 7 remaining items

  16. cyangle commented on Feb 27, 2026

    @cyangle

    It looks like opencode constructs 2 system messages by default, and qwen 3.5's default template expects 1 system message at the beginning only.

    await Plugin.trigger(
    "experimental.chat.system.transform",
    { sessionID: input.sessionID, model: input.model },
    { system },
    )
    // rejoin to maintain 2-part structure for caching if header unchanged
    if (system.length > 2 && system[0] === header) {
    const rest = system.slice(1)
    system.length = 0
    system.push(header, rest.join("\n"))
    }

  17. paulgear commented on Feb 27, 2026

    @paulgear

    It looks like opencode constructs 2 system messages by default, and qwen 3.5's default template only expects 1 system message at the beginning only.

    I made a fix for this at #15018 - maybe if we can get some 👍 on it it might get merged.

  18. 0xDEADBEEF-td commented on Mar 3, 2026

    @0xDEADBEEF-td

    Thanks for this @paulgear - I built with your fix and I can finally use opencode again with the new Qwen 3.5 models.

    Would love to run a release from anomalyco, though. Will keep an eye on this to see when your fix gets merged.

    Thanks again!

  19. Thump604 commented on Mar 13, 2026

    @Thump604

    Reproducing with:

    • OpenCode v1.2.24/1.2.25
    • vllm-mlx v0.2.6 serving Qwen3.5-27B-8bit (MLX quantized)
    • macOS Darwin 25.3.0, Apple Silicon M2 Ultra

    The root cause is that OpenCode sends multiple system messages in its API requests. The Qwen 3.5 official chat template (chat_template.jinja shipped with the model weights) explicitly rejects this at line 84-85:

    {%- if message.role == "system" %}
        {%- if not loop.first %}
            {{- raise_exception('System message must be at the beginning.') }}
        {%- endif %}

    The template only allows 4 roles: system, user, assistant, tool. Any other role (like developer) hits the catch-all at line 143-144:

    {%- else %}
        {{- raise_exception('Unexpected message role.') }}

    This affects ALL Qwen 3.5 models (27B, 35B, 122B) across ALL servers that use the model's built-in template (vllm-mlx, vllm, llama.cpp without custom template override).

    Server-side workaround (custom template) exists but shouldn't be necessary — OpenCode should consolidate system messages for non-Anthropic providers as PR #15018 proposes.

    +1 for PR #15018 — this is a critical blocker for Qwen 3.5 users.

  20. jacekpoplawski commented on Mar 13, 2026

    @jacekpoplawski

    Maybe the new issue should be created because it’s not just about Ministral, it’s a much broader problem.

  21. removed
    bugSomething isn't working
    on May 3, 2026
  22. github-actions commented on Jul 3, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions