tyzbit / tyzbit/acars-processor

OpenAI filter sends 3 consecutive system messages → 400 on strict chat templates (e.g. Qwen3 via llama.cpp), silently dropping all messages

Open Beginner friendly
#45 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Go
Stars
9
Forks
0
PR merge metrics
No merged PRs in 30d

Description

Summary

OpenAIFilterer.Filter builds its request with three consecutive system messages followed by the user message. Many chat templates only allow a single, leading system message and raise on anything else. When the backend enforces this (e.g. Qwen3 served by llama.cpp with --jinja), every request returns HTTP 400 before the model runs. Because the filter returns FilterOnFailure on any error — and FilterOnFailure defaults to trueevery message is silently filtered out. For an OpenAI-filter → Discord pipeline, this looks like the integration has simply stopped forwarding anything.

Version: v1.3.2 (latest). Backend: llama.cpp server (ghcr.io/ggml-org/llama.cpp:server-*) with --jinja, Qwen3-family GGUF.

Error returned by the backend

POST ".../chat/completions": 400 Bad Request
{"error":{"code":400,"message":"Unable to generate parser for this template. Automatic parser generation failed:
------------
While executing CallExpression at line 85, column 32 in source:
...first %}\n            {{- raise_exception('System message must be at the beginnin...
                                           ^
Error: Jinja Exception: System message must be at the beginning.","type":"invalid_request_error"}}

Followed by:

WARN  error filtering with OpenAIFilterer in step number 5: ... 400 Bad Request ...
INFO  message ... was filtered in step 5 by OpenAIFilterer

Root cause

In filter_openai.go, the request is assembled as:

chatCompletion, err := client.Chat.Completions.New(context.TODO(),
    openai.ChatCompletionNewParams{
        Messages: openai.F([]openai.ChatCompletionMessageParamUnion{
            openai.SystemMessage(OpenAISystemPrompt),     // system #1
            openai.SystemMessage(o.UserPrompt),           // system #2
            openai.SystemMessage(OpenAIFinalInstructions),// system #3
            openai.UserMessage(ms),
        }),
        ...

Three system messages in a row. Strict templates (Qwen3, and others) reject non-leading/repeated system messages via raise_exception(...), so the call 400s. On that error the function returns o.FilterOnFailure, which defaults to true, so the message is dropped.

Suggested fix

The sibling annotator already does this correctly — it concatenates everything into a single system message, see annotator_openai.go:125:

openai.SystemMessage(OpenAIAnnotatorFirstInstructions + a.UserPrompt + OpenAIAnnotatorFinalInstructions),
openai.UserMessage("Here is the message to evaluate:\n" + msg),

The filter should mirror this: collapse OpenAISystemPrompt + o.UserPrompt + OpenAIFinalInstructions into one SystemMessage (or move the instructions into the user turn). That keeps a single leading system message and works across both lenient and strict templates.

Impact / severity

  • Silent: with FilterOnFailure: true (the default), there is no user-facing error — messages just stop flowing, easily mistaken for "no matching traffic."
  • Affects any OpenAI-compatible backend that enforces single-leading-system templates; notably llama.cpp --jinja with Qwen3 models, a common self-hosted setup that the README's http://llama-server:8080/v1 examples point at.

Repro

  1. Run llama.cpp server with a Qwen3 GGUF and --jinja.
  2. Configure an OpenAI filter step pointing at it (URL: http://.../v1, any UserPrompt).
  3. Send any message with text. Backend returns 400 (System message must be at the beginning); the message is filtered out.

Workaround (until fixed)

Override the model's chat template to a lenient one (e.g. --chat-template chatml) so repeated system messages are accepted — at the cost of the model's native (thinking/tool-call) template — or set FilterOnFailure: false to fail open. Neither is a real fix; the message construction above is the bug.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in filter_openai.go, where OpenAIFilterer.Filter assembles the chat completion, and compare it with annotator_openai.go:125. Reproduce with the Qwen3 llama.cpp setup described in the issue, then verify that the request uses one leading system message and no longer returns HTTP 400 or silently filters the message.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
api, backend
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.