open-webui / open-webui/computer
bug: repeated context compaction can drop every user message and break Qwen/vLLM
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 569
- Forks
- 79
- PR merge metrics
- No merged PRs in 30d
Description
Summary
On cptr 0.9.12, a long agentic Chat Completions task can fail after repeated automatic context compaction when the retained keep_zone contains only assistant and tool messages.
With a Qwen model served by vLLM through LiteLLM, the next request fails with:
litellm.BadRequestError: Hosted_vllmException -
{"error":{"message":"No user query found in messages.","type":"BadRequestError","code":400}}
Raising chat.compact_token_threshold only delays the failure.
Environment
- Computer/cptr: 0.9.12
- Connection: OpenAI-compatible Chat Completions
- Gateway: LiteLLM
hosted_vllm - Backend: vLLM
- Model group: Qwen
- OS: Windows (but the compaction logic is platform-independent)
Reproduction
- Start a task from one normal user request.
- Let the agent execute a long sequence of tool calls and tool results.
- Allow automatic compaction to run more than once.
- On a later compaction, the only retained user message can fall into the new drop zone.
- The next upstream request contains a system prompt/summary followed only by assistant/tool turns.
A sanitized real trace reproduced the second compaction as:
messages before split: 263
split index: 158
messages kept: 105
user messages kept: 0
Root cause
In cptr/utils/chat_task.py:
_find_safe_split()protects tool-call/tool-result boundaries and keeps at least two messages.- It does not guarantee that
keep_zonecontains a top-leveluserturn. - After compaction,
messages = keep_zone. api_messagesis then sent to the provider unchanged with respect to roles.
For a task that began with one user prompt followed by many assistant/tool iterations, repeated 40% retention can eventually remove every user turn.
Expected behavior
Every compacted continuation sent through Chat Completions should contain a real top-level user anchor, while preserving the retained assistant/tool ordering.
Suggested fix
Add a small invariant immediately before constructing ChatCompletionForm:
def _ensure_user_query(messages: list[dict]) -> list[dict]:
if any(message.get("role") == "user" for message in messages):
return messages
return [
{
"role": "user",
"content": (
"Continue the task using the conversation summary "
"and the tool results below."
),
},
*messages,
]
# After removing internal message IDs:
api_messages = _ensure_user_query(api_messages)
This keeps normal histories unchanged and adds one synthetic continuation turn only when compaction removed every user message.
Regression tests
- A history that already has a user message is returned unchanged.
- An assistant/tool-only history gets one non-empty user message prepended.
- Existing assistant/tool messages retain their exact order and content.
- Repeated compaction of a single-user agentic task still produces a provider request containing a user turn.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in cptr/utils/chat_task.py by reading _find_safe_split and the code that removes internal message IDs before constructing ChatCompletionForm. Verify the existing compaction flow and add regression coverage for user-preserving, assistant/tool-only, ordering, and repeated-compaction cases. Done means every compacted Chat Completions request contains a top-level user turn without changing histories that already have one.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- api, backend, testing-qa
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100