fix(llm): System-Nachrichten zu einer fuehrenden zusammenfuehren (Qwen3-Template)
Qwen3 verlangt genau eine System-Nachricht ganz am Anfang. Sobald ein Nutzer Erinnerungen hatte, injizierte das Gateway eine zweite System-Nachricht zusaetzlich zum Sprach-System-Prompt -> 400 "System message must be at the beginning". Der lokale Provider fuehrt jetzt Sprach-Prompt + System-Inhalte aus der History (z. B. Nutzer-Erinnerungen) zu einer einzigen System-Nachricht zusammen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
76df695111
commit
d59afa4dd9
2 changed files with 49 additions and 6 deletions
|
|
@ -25,13 +25,26 @@ class LocalOpenAICompatibleLLM(LLMProvider):
|
||||||
self.temperature = temperature
|
self.temperature = temperature
|
||||||
|
|
||||||
def _build_messages(self, text: str, history: list[dict] | None) -> list[dict]:
|
def _build_messages(self, text: str, history: list[dict] | None) -> list[dict]:
|
||||||
messages: list[dict] = []
|
# Manche Chat-Templates (z. B. Qwen3) erlauben nur EINE System-Nachricht,
|
||||||
# Sprach-System-Prompt zuerst (knappe, vorlesbare Antworten). Etwaige
|
# ganz am Anfang. Daher den Sprach-System-Prompt und etwaige System-
|
||||||
# System-Nachrichten aus der History (z. B. Nutzer-Erinnerungen) bleiben erhalten.
|
# Nachrichten aus der History (z. B. Nutzer-Erinnerungen) zu einer einzigen
|
||||||
|
# fuehrenden System-Nachricht zusammenfuehren.
|
||||||
|
system_parts: list[str] = []
|
||||||
if self.system_prompt:
|
if self.system_prompt:
|
||||||
messages.append({"role": "system", "content": self.system_prompt})
|
system_parts.append(self.system_prompt)
|
||||||
if history:
|
rest: list[dict] = []
|
||||||
messages.extend(history)
|
for msg in history or []:
|
||||||
|
if msg.get("role") == "system":
|
||||||
|
content = (msg.get("content") or "").strip()
|
||||||
|
if content:
|
||||||
|
system_parts.append(content)
|
||||||
|
else:
|
||||||
|
rest.append(msg)
|
||||||
|
|
||||||
|
messages: list[dict] = []
|
||||||
|
if system_parts:
|
||||||
|
messages.append({"role": "system", "content": "\n\n".join(system_parts)})
|
||||||
|
messages.extend(rest)
|
||||||
messages.append({"role": "user", "content": text})
|
messages.append({"role": "user", "content": text})
|
||||||
return messages
|
return messages
|
||||||
|
|
||||||
|
|
|
||||||
30
tests/test_local_llm_messages.py
Normal file
30
tests/test_local_llm_messages.py
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
from app.providers.llm.local_openai_compatible import LocalOpenAICompatibleLLM
|
||||||
|
|
||||||
|
|
||||||
|
def _llm(system_prompt="Sei kurz."):
|
||||||
|
return LocalOpenAICompatibleLLM("http://x/v1", "k", "m", system_prompt=system_prompt)
|
||||||
|
|
||||||
|
|
||||||
|
def test_single_system_message_when_history_has_system():
|
||||||
|
llm = _llm()
|
||||||
|
history = [
|
||||||
|
{"role": "system", "content": "Was du ueber den Nutzer weisst:\n- heisst Anna"},
|
||||||
|
{"role": "user", "content": "Hallo"},
|
||||||
|
{"role": "assistant", "content": "Hi Anna"},
|
||||||
|
]
|
||||||
|
msgs = llm._build_messages("Wie geht es dir?", history)
|
||||||
|
|
||||||
|
# Genau EINE System-Nachricht, ganz am Anfang (Qwen3-Template-Anforderung).
|
||||||
|
assert sum(1 for m in msgs if m["role"] == "system") == 1
|
||||||
|
assert msgs[0]["role"] == "system"
|
||||||
|
assert "Sei kurz." in msgs[0]["content"]
|
||||||
|
assert "heisst Anna" in msgs[0]["content"]
|
||||||
|
# Reihenfolge der Nicht-System-Nachrichten bleibt erhalten, User zuletzt.
|
||||||
|
assert [m["role"] for m in msgs] == ["system", "user", "assistant", "user"]
|
||||||
|
assert msgs[-1] == {"role": "user", "content": "Wie geht es dir?"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_system_message_without_prompt_or_history():
|
||||||
|
llm = _llm(system_prompt="")
|
||||||
|
msgs = llm._build_messages("Hallo", None)
|
||||||
|
assert msgs == [{"role": "user", "content": "Hallo"}]
|
||||||
Loading…
Add table
Add a link
Reference in a new issue