DeepSeek V4 zweite Runde 400: Logs richtig prüfen

Am 15.08.2026 gilt für DeepSeek V4 zweite Runde 400 eine klare erste Prüfregel: Wenn der erste Tool-Aufruf erfolgreich ist und erst die nächste Anfrage mit HTTP 400 abbricht, sollte nicht zuerst erneut versucht, skaliert oder global ein Feld kopiert werden. Die offizielle DeepSeek-Dokumentation verlangt im Thinking-Mode bei Tool-Aufrufen die vollständige Rückgabe von reasoning_content in den folgenden Requests. Bei einem vLLM-Endpunkt kann dagegen reasoning der maßgebliche Feldname sein. Die richtige Maßnahme ist deshalb ein Feldvergleich derselben Sitzung: Modellantwort, Arbeitsspeicher, Datenbank, Gateway und finaler Request. (DeepSeek: Thinking Mode und Tool Calls)
Zuletzt aktualisiert am 15.08.2026; die Angaben wurden anhand der offiziellen DeepSeek-API-Dokumentation sowie der aktuellen vLLM-Dokumentation zur Reasoning-Ausgabe geprüft.
Diese Anleitung richtet sich an Entwickler, die einen mehrstufigen function calling-Loop betreiben und erst nach dem ersten erfolgreichen Tool-Aufruf einen 400-Fehler sehen. Ebenso angesprochen sind Backend-Teams für API-Gateway, Sitzungsdatenbank und SDK-Wrapper sowie Plattformteams, die zwischen der offiziellen DeepSeek API und einem vLLM-Endpunkt umschalten müssen.
Der Fehlerzeitpunkt trennt Tool-Definition und Verlauf
Ein typisches, vollständig anonymisiertes Muster sieht so aus:
Request 1:
user -> assistant
assistant:
content: "Ich ermittle zunächst den erforderlichen Wert."
reasoning_content: "<redacted>"
tool_calls: [call_abc]
Request 2:
user -> assistant -> tool -> assistant
HTTP 400:
reasoning_content fehlt oder passt nicht zum Ziel-Endpunkt
Wenn der erste Request eine gültige tool_call erzeugt, sind mehrere Komponenten bereits nachweislich funktionsfähig:
- Die Tool-Definition wurde akzeptiert.
- Modellname und Authentifizierung waren für den ersten Aufruf gültig.
- Die erste Modellantwort konnte empfangen und verarbeitet werden.
- Der Agent konnte zumindest einmal eine Tool-Nachricht erzeugen.
- Der Fehler entsteht wahrscheinlich beim Wiederaufbau des Gesprächsverlaufs.
Das ist noch kein endgültiger Beweis. Ein ungültiger tool_call_id, eine nicht unterstützte Option wie tool_choice oder ein falsch serialisiertes content kann ebenfalls erst in der zweiten Runde sichtbar werden. Der Zeitpunkt verändert jedoch die Reihenfolge der Untersuchung: Zuerst wird der übertragene Verlauf geprüft, danach werden Parameter und Modellserver untersucht.
Die erste Assistant-Nachricht mit Tool-Aufruf muss als Einheit betrachtet werden. Dazu gehören die Rolle, der sichtbare Inhalt, das Reasoning-Feld und die Tool-Aufrufdaten. Danach folgt die zugehörige tool-Nachricht mit passender ID. Wird beim Aufbau der nächsten Anfrage nur content übernommen, sieht der Verlauf für den Client zwar plausibel aus, ist für den Thinking-Mode aber möglicherweise unvollständig.
Die DeepSeek-Anleitung unterscheidet zwischen normalen Thinking-Mode-Antworten und Tool-Aufrufen. Besonders bei einer Assistant-Nachricht, die einen Tool-Aufruf enthält, muss der für die Fortsetzung erforderliche Reasoning-Anteil erhalten bleiben. Das erklärt, warum ein einfacher Chat-Aufruf funktioniert, während ein Agent-Loop nach der ersten Werkzeugausführung scheitert. (DeepSeek: Tool-Calls-Leitfaden)
Warum fehlt reasoning_content im finalen Request?
Der schnellste Test erfolgt unmittelbar vor dem Netzwerkversand. Dabei wird nicht der vollständige Denktext protokolliert, sondern nur die Feldstruktur:
assistant_message = messages[assistant_index]
assert assistant_message.get("role") == "assistant"
assert assistant_message.get("tool_calls")
assert assistant_message.get("reasoning_content") is not None
Ein häufiger Fehler liegt in einer verkürzten Rekonstruktion:
messages.append({
"role": "assistant",
"content": response_message.get("content"),
"tool_calls": response_message.get("tool_calls"),
})
In diesem Beispiel wird reasoning_content nicht übernommen. Der nächste Request enthält somit zwar den sichtbaren Inhalt und den Tool-Aufruf, aber nicht den vollständigen Assistant-Kontext.
Für die DeepSeek API muss die Rekonstruktion das Feld ausdrücklich berücksichtigen:
messages.append({
"role": "assistant",
"content": response_message.get("content"),
"reasoning_content": response_message.get("reasoning_content"),
"tool_calls": response_message.get("tool_calls"),
})
messages.append({
"role": "tool",
"tool_call_id": tool_call["id"],
"content": sanitized_tool_result,
})
Der Code zeigt keine Zugangsdaten, keinen vollständigen Reasoning-Text und kein reales Tool-Ergebnis. In produktiven Logs genügen zunächst vier Eigenschaften:
- Feld vorhanden: ja oder nein
- Datentyp: String,
nulloder Objekt - Länge oder Hash des Inhalts
- Nachrichtenindex und zugehörige
tool_call_id
Die offizielle Chat-Completion-Spezifikation führt reasoning_content als Assistant-Feld für den Thinking-Mode, tool_calls als vom Modell erzeugte Aufrufe und tool_call_id als Zuordnung für die Antwort eines Tools. Bei Funktionsargumenten wird außerdem ausdrücklich eine Validierung vor der Ausführung empfohlen, weil generierte Argumente nicht zwangsläufig dem definierten Schema entsprechen. (DeepSeek: Chat-Completion-Schema)
Vier typische Verluststellen
SDK-Konvertierung: Das SDK liefert ein Antwortobjekt mit Zusatzfeldern. Eine eigene to_dict()-Methode übernimmt aber nur Standardfelder.
Message-Builder: Das interne Nachrichtenschema kennt lediglich role, content und tool_calls. Unbekannte Eigenschaften werden beim Erstellen des nächsten Objekts verworfen.
JSON-Säuberung: Ein Serializer entfernt Felder, die nicht in einer Allowlist stehen. Manche Implementierungen löschen zusätzlich null, leere Strings oder unbekannte Eigenschaften.
Streaming-Merge: Reasoning-Deltas und Content-Deltas werden getrennt empfangen. Beim Zusammenführen wird nur content in das finale Assistant-Objekt geschrieben.
Gerade beim Streaming muss der Vergleich nach dem letzten Chunk erfolgen. Ein einzelner früher Log-Eintrag kann zeigen, dass reasoning_content empfangen wurde, obwohl das später gespeicherte Gesamtobjekt das Feld nicht mehr enthält. Die DeepSeek-API-Spezifikation führt reasoning_content auch in den Streaming-Deltas als eigenes Feld. (DeepSeek: Streaming-Schema für Chat Completions)
DeepSeek API und vLLM benötigen unterschiedliche Ausgangsfelder
Kann vLLM reasoning direkt an die DeepSeek API gesendet werden?
Nein, nicht ohne Prüfung und gegebenenfalls Mapping. reasoning und reasoning_content können semantisch denselben internen Inhalt beschreiben, sind aber nicht automatisch austauschbare Vertragsfelder.
| Prüfpunkt | Offizielle DeepSeek API | vLLM-Endpunkt |
|---|---|---|
| Maßgebliches Reasoning-Feld | reasoning_content |
in aktuellen vLLM-Dokumenten reasoning |
| Tool-Aufruf | tool_calls mit ID und Funktionsdaten |
abhängig von Modell, Parser und Serverversion |
| Nächster Request | Reasoning des Tool-Aufruf-Assistant zurückgeben | konkrete Protocol-Definition des Endpunkts prüfen |
| Alter Feldname | nach offizieller API-Spezifikation beurteilen | nicht als dauerhaft garantiert annehmen |
| Adapterstrategie | reasoning intern nach reasoning_content abbilden |
das tatsächlich erwartete Feld ausgeben |
Die aktuelle vLLM-Dokumentation nennt reasoning als Ausgabefeld und weist darauf hin, dass reasoning_content der frühere Name war. Daraus folgt jedoch nicht automatisch, dass jede vLLM-Version jedes alte Eingabeformat akzeptiert. Version, Modelladapter, Reasoning-Parser und OpenAI-kompatible Schnittstelle werden gemeinsam geprüft. (vLLM: aktuelle Dokumentation zu Reasoning Outputs)
vLLM dokumentiert außerdem, dass Tool-Aufrufe aus dem Feld content geparst werden, nicht aus reasoning. Ein vorhandenes Reasoning-Feld ersetzt daher keine gültigen tool_calls. Auch ein Client, der Reasoning korrekt speichert, kann weiterhin scheitern, wenn die Funktionsdaten oder ihre IDs beschädigt sind. (vLLM: Tool Calling mit Reasoning)
Ein robustes internes Nachrichtenmodell verwendet nur ein neutrales Feld:
internal_message = {
"role": "assistant",
"content": response_content,
"reasoning": normalized_reasoning,
"tool_calls": normalized_tool_calls,
}
Am Ausgang wird der Vertrag des Zielsystems angewendet:
def to_deepseek_api(message):
return {
"role": "assistant",
"content": message["content"],
"reasoning_content": message["reasoning"],
"tool_calls": message["tool_calls"],
}
def to_vllm(message, protocol):
output = {
"role": "assistant",
"content": message["content"],
"tool_calls": message["tool_calls"],
}
if protocol == "reasoning":
output["reasoning"] = message["reasoning"]
else:
output["reasoning_content"] = message["reasoning"]
return output
Die Zuordnung sollte nicht nur vom Hostnamen abhängen. Sinnvoll sind eine explizite Endpunktklasse, die eingesetzte Version und ein Protocol-Flag. Bei jedem Deployment wird außerdem ein kleiner Direkt-Test ausgeführt, der prüft:
- Welches Feld liefert der Server?
- Welches Feld akzeptiert er im Folge-Request?
- Werden beide Felder akzeptiert, ignoriert oder abgewiesen?
- Bleibt das Feld nach Streaming und JSON-Serialisierung erhalten?
Ein globales Doppelschreiben von reasoning und reasoning_content kann als kurzfristiger Diagnoseversuch dienen. Als dauerhafte Lösung ist es unsauber. Es verschleiert, welcher Endpunktvertrag tatsächlich verwendet wird, und kann bei strikter Schema-Validierung neue 400-Fehler erzeugen.
Das Feld bleibt intern erhalten, verschwindet aber im Geschäftsweg
Wenn die interne Nachricht korrekt aussieht, wird die Übertragung in einzelnen Knoten geprüft. Jede Station erhält dieselbe Korrelation-ID, denselben Nachrichtenindex und eine reduzierte Feldübersicht.
1. Nach der Modellantwort
Direkt nach der Antwort wird die Struktur dokumentiert:
{
"node": "model_response",
"message_index": 3,
"role": "assistant",
"fields": [
"role",
"content",
"reasoning_content",
"tool_calls"
],
"reasoning_length": "[redacted]",
"tool_call_ids": ["hash:abc"]
}
Der vollständige Denktext gehört nicht in ein gewöhnliches Anwendungslog. Ein Hash oder eine grobe Längenklasse reicht für den Nachweis, dass ein Inhalt weitergegeben wurde.
2. Nach der SDK-Serialisierung
Verglichen werden Objekt und erzeugtes JSON. Verdächtig sind eigene Serialisierer, Dataclasses mit eingeschränkter Feldliste und Modelle mit einer Einstellung wie „zusätzliche Felder verbieten“. Auch ein Wechsel zwischen Bibliotheksversionen kann das Antwortobjekt verändern.
3. Nach dem API-Gateway
Prüfen Sie Request-Allowlist, JSON-Schema, Body-Transformation und mögliche Feldmasken. Ein Gateway kann aus dem eingehenden Objekt einen neuen Body erzeugen und dabei reasoning_content oder reasoning still entfernen. Ein OpenAI-kompatibler vLLM-Server wird über /v1/chat/completions angesprochen; die tatsächlich unterstützten Parameter müssen aber zur installierten Serverversion passen. (vLLM: OpenAI-kompatibler Server)
4. Nach der Datenbank
Ein Schema mit nur role und content verliert Reasoning und Tool-Metadaten, wenn diese nicht in einer strukturierten JSON-Spalte oder einer eigenen Nachrichtentabelle gespeichert werden. Bei Dokumentdatenbanken sind Projektionen, Feldmasken und Versionen des Persistenzmodells zu prüfen.
Die Nachrichtenreihenfolge muss ebenfalls erhalten bleiben. Ein Datensatz, der Inhalte speichert, aber Array-Positionen oder Tool-ID-Zuordnungen nicht zuverlässig bewahrt, kann den nächsten Request logisch beschädigen.
5. Nach dem Queue-Consumer
Ein älterer Consumer kann ein anderes Nachrichtenmodell verwenden als der Produzent. Besonders fehleranfällig ist die Zusammenführung von Streaming-Ereignissen: Ein späteres Update überschreibt die vollständige Assistant-Nachricht mit einer kürzeren Version, in der nur content enthalten ist.
Für die Untersuchung werden jeweils die Feldmengen verglichen:
model_response: role, content, reasoning_content, tool_calls
memory: role, content, reasoning_content, tool_calls
database: role, content, tool_calls
gateway_out: role, content, tool_calls
final_request: role, content, tool_calls
In diesem Muster verschwindet das Feld zwischen Datenbank und Gateway. Die Ursache liegt dann nicht im Modell, sondern in Persistenz, Projektion oder Request-Transformation.
Reasoning kann interne Prompts oder sensible Tool-Informationen enthalten. Produktionslogs benötigen daher Redaction-Regeln, begrenzte Aufbewahrung und rollenbasierte Zugriffe. Die Datenschutzerklärung von ProxyMac ist der passende Bezugspunkt, wenn Test- und Betriebsdaten personenbezogene Inhalte enthalten können.
Wenn die Felder stimmen, werden Nachrichtenkette und Parameter geprüft
Ein vorhandenes Reasoning-Feld schließt andere Fehler nicht aus. Die minimale Folge muss logisch zusammenpassen:
user
assistant mit tool_calls
tool mit passender tool_call_id
anschließende Modellanfrage
Typische Abweichungen sind:
tool_call_idpasst nicht zur ID der Assistant-Nachricht.- Die
tool-Nachricht steht vor der zugehörigen Assistant-Nachricht. - Mehrere Tool-Aufrufe wurden erzeugt, aber nur ein Ergebnis wird zurückgegeben.
- Das Framework serialisiert
contentals inkompatiblen Nullwert. - Das Tool-Ergebnis wird vor der Assistant-Nachricht eingefügt.
tool_choicewird im Thinking-Mode ohne Prüfung gesetzt.- Ein Framework fügt zwischen Tool-Ergebnis und nächstem Modellaufruf eine zusätzliche Systemnachricht ein.
Damit nicht jeder 400-Fehler als Feldproblem bezeichnet wird, muss die Schlussfolgerung durch mindestens eines von drei Beweisstücken gestützt werden:
- finaler JSON-Body mit Feldabweichung,
- eindeutige Fehlermeldung des Zielservers,
- reproduzierbarer Unterschied zwischen korrektem und fehlerhaftem Minimal-Request.
Die offizielle DeepSeek-Spezifikation führt tool_call_id als erforderliche Zuordnung der Tool-Nachricht und beschreibt tool_calls als strukturierte Funktionsaufrufe. Deshalb werden tool_choice, Rollenfolge und Content-Serialisierung erst nach dem Feldvergleich geprüft. (DeepSeek: API-Referenz für Tool- und Assistant-Nachrichten)
So wird ein Minimal-Log für die zweite Runde erstellt
Ein belastbarer Fehlerdatensatz benötigt weder den vollständigen Denktext noch reale Tool-Ergebnisse. Er benötigt die Unterschiede zwischen den Knoten.
- Neue Sitzung anlegen: Verwenden Sie eine isolierte Session-ID und genau ein ungefährliches Test-Tool.
- Erste Antwort sichern: Erfassen Sie Rollen, Feldnamen, Tool-Call-ID sowie Hash oder Länge des Reasoning-Feldes.
- Tool-Ergebnis anonymisieren: Verwenden Sie einen festen synthetischen Wert ohne Kundendaten.
- Übergänge markieren: Protokollieren Sie
model_response,memory,database,gateway_outundfinal_request. - Feldmengen vergleichen: Prüfen Sie Feldnamen, Datentypen, Nachrichtenindex, Array-Länge und Nullwerte.
- DeepSeek direkt testen: Senden Sie den minimalen Verlauf ohne Gateway und Queue an die offizielle API.
- vLLM direkt testen: Wiederholen Sie denselben semantischen Verlauf mit dem tatsächlich eingesetzten vLLM-Protokoll.
- Geschäftskette wiederholen: Führen Sie den Test anschließend durch SDK, Datenbank, Gateway und Queue.
- Differenz dokumentieren: Halten Sie Endpunktklasse, Modell, Version, Adapteränderung und Request-Differenz fest.
Für die beiden Direktverbindungen gelten getrennte Erwartungen. Ein erfolgreicher DeepSeek-Test beweist nicht, dass derselbe JSON-Body für vLLM geeignet ist. Ein lokaler vLLM-Erfolg beweist umgekehrt nicht, dass die offizielle API ein vLLM-spezifisches Feld akzeptiert.
Die nachhaltige Reparatur sitzt im Adapter
Die Korrektur sollte an der Grenze zwischen internem Nachrichtenmodell und Ziel-Endpunkt erfolgen:
- Beim Eingang werden
reasoning_contentundreasoningin ein internes Feld normalisiert. - Vor dem Versand prüft der Adapter Rollenfolge, Tool-ID, erwartetes Reasoning-Feld und verbotene Parameter.
- Beim Ausgang wird genau das Feld erzeugt, das der Zielvertrag verlangt.
- Die konkrete Endpunktversion wird zusammen mit der Request-Differenz protokolliert.
Eine geeignete Prüfspur enthält beispielsweise:
session_id: anonymisiert
endpoint_kind: deepseek_api | vllm
protocol_version: festgehalten
message_count: festgehalten
assistant_tool_call_index: festgehalten
reasoning_field: reasoning_content | reasoning | missing
tool_call_ids: gehasht
request_diff: gespeichert
Vollständige Reasoning-Texte gehören nicht automatisch in Langzeitlogs. Ein Hash belegt, dass ein Feld erhalten blieb, ohne den Inhalt zu archivieren. Tool-Ergebnisse werden für Regressionstests durch feste Testwerte ersetzt.
Wenn ein Fehler nur in einer bestimmten vLLM-Version oder mit einem bestimmten Parser auftritt, darf daraus keine allgemeine Aussage über alle vLLM-Installationen abgeleitet werden. Die Protokolldefinition und das tatsächliche Deployment werden zusammen geprüft. Nach einem Upgrade wird derselbe Minimal-Request erneut abgesendet.
Eine reproduzierbare Testumgebung ist besser als blindes Wiederholen
Der aktuelle Ansatz mit wiederholten Requests, wechselnden Modellparametern oder manuellen lokalen Tests hat drei konkrete Schwächen:
- Ein Gateway kann das Feld weiterhin unbemerkt entfernen.
- Ein selbst verwalteter vLLM-Endpunkt kann sich nach einer Versionsänderung anders verhalten.
- Eine lokale Umgebung bildet Datenbank, Queue, Proxy und Streaming-Merge häufig nicht vollständig ab.
Eine feste Regression-Umgebung speichert deshalb die fehlerhafte Sitzung, die erwarteten Feldmengen und die Unterschiede zwischen den Endpunkten. Für das Betriebsmanagement kann die ProxyMac-Konsole genutzt werden, während sensible Reasoning-Inhalte aus Testprotokollen entfernt bleiben sollten.
Wenn zusätzlich macOS-Clients, Xcode-Automatisierung oder dauerhaft laufende Agent-Aufgaben Bestandteil der Teststrecke sind, kann eine gemietete Mac-Umgebung von ProxyMac gegenüber wechselnden lokalen Geräten eine stabilere Ausführungsbasis bieten. Für einen kurzen Feldtest genügt dagegen eine kleine, reproduzierbare Umgebung. Bei dauerhaft hoher Rechenlast oder notwendiger physischer Spezialhardware ist ein eigenes System weiterhin sachlich zu vergleichen. Den passenden Betriebs- und Zugangsrahmen erläutert der ProxyMac-Hilfebereich.