Dify Chatflow antwortet erst am Ende: Agent, Fail Branch und Answer prüfen
Wenn eine Dify-Chatflow-Antwort erst nach Abschluss als Block erscheint, vergleichen Sie denselben Prompt und dasselbe Modell auf vier Ebenen: erstes Modell-Token, SSE-Transport, agent_log und nutzersichtbare message-Chunks. Der Leitfaden enthält einen ausführbaren Zeitmesser, Kontrollen für Fail Branch und die Variablenreihenfolge in Answer sowie eine Entscheidung, die Fehlerbehandlung während der Diagnose erhält.
Inhalt

Wenn Dify Chatflow die gesamte Antwort erst am Ende als Block zeigt, beweist das nicht, dass das Upstream-Modell nicht streamt. Auch ein Provider-Wechsel ist nicht automatisch die Lösung. Halten Sie einen kurzen Prompt, ein Modell und die Agent-Konfiguration konstant und vergleichen Sie vier Zeitpunkte: erstes Upstream-Token, SSE-Ereignisse, agent_log und nutzersichtbare message-Chunks.
Gesucht wird die Ebene, auf der Text wartet oder gepuffert wird. Dafür müssen Sie die Fehlerbehandlung in Produktion nicht entfernen. Fail Branch wird nur in einer Testkopie als Kontrollvariable deaktiviert.
Ordnen Sie das Ereignismuster einer Ebene zu
| Beobachtung | Zuerst prüfen | Nächster Test |
|---|---|---|
| Auch LLM → Answer liefert keinen frühen Text | Modell, Provider, Gateway oder Modellparameter | Kurze Textanfrage und Streaming-Unterstützung des Modells prüfen |
workflow_started, node_started und andere Ereignisse kommen insgesamt spät | Proxy, Gateway oder SSE-Transport | Dieselbe Anfrage direkt an Dify senden und Antwortpufferung ausschließen |
agent_log kommt während des Laufs, message aber erst als Schlussblock | Pfad Agent → Answer; Fail Branch als Kontrollvariable | Produktions-Fallback erhalten und Zeiten in einem geklonten Flow vergleichen |
message beginnt erst nach Abschluss einer anderen Variable | Frühere ungelöste Variable in Answer | Variablen umordnen oder mehrere Answer-Knoten für Stufen verwenden |
Laut Answer-Dokumentation streamt der Knoten entsprechend der Verfügbarkeit von Variablen. Er gibt Inhalt bis zur ersten ungelösten Variable aus, wartet auf sie und setzt danach fort. Entscheidend ist daher die Reihenfolge im Answer-Text, nicht die Ausführungsreihenfolge der Upstream-Knoten.
Führen Sie vier Kontrollen mit nur einer Änderung aus
Duplizieren Sie den vorhandenen Chatflow für Tests. Notieren Sie veröffentlichte Version, Model ID, temperature, Agent-Strategie, Tools, maximale Iterationen, Memory-Einstellungen und Fehlerbehandlung. Verwenden Sie in jedem Lauf denselben kurzen Prompt, zum Beispiel:
Schreibe 12 nummerierte Ein-Satz-Prüfungen zur Diagnose einer Streaming-Antwort.
Die Antwort soll lang genug für einen messbaren Zeitraum sein, aber keine echten Nutzerdaten enthalten.
Wiederholen Sie vor den Kontrollen A, B, C und D stets dieselbe Folge: nur die Änderung dieser Runde anwenden, den aktuellen Stand der Testkopie veröffentlichen, Run-Label und veröffentlichte Workflow-Version notieren und anschließend den auf der API-Access-Seite dieser Kopie angezeigten Service API Base (der vollständige Pfad endet meist auf /v1) mit ihrem App API Key verwenden. Nutzen Sie nicht den Schlüssel der Original-App. Ohne workflow_id führt Dify die neueste veröffentlichte Version aus; ein Aufruf vor der Veröffentlichung kann daher mehrfach dieselbe ältere Version testen.
Kontrolle A: nur LLM → Answer
Leiten Sie in der Testkopie die Ausgabe desselben Modells direkt vom LLM-Knoten zu Answer, ohne Agent und Tools. Dify dokumentiert, dass LLM-Knoten standardmäßig streamen. Wenn message weiterhin erst am Ende kommt, ist Agent noch nicht bewiesen. Prüfen Sie zuerst Provider, OpenAI-compatible Gateway, Reverse Proxy und Answer-Reihenfolge.
Kontrolle B: aktueller Agent mit Fail Branch
Stellen Sie den realen Agent-Pfad und den Produktions-Fallback wieder her. Das ist die Basislinie. Entfernen Sie die Wiederherstellung nicht nur für eine schnellere Anzeige: Fail Branch führt nach einem Fehler einen separaten Pfad aus, etwa Protokollierung, Benachrichtigung, einen anderen Ansatz oder einen Backup-Dienst.
Kontrolle C: Fail Branch nur in der Testkopie deaktivieren
Führen Sie diesen Test nur aus, wenn Ihr aktueller Agent dieselbe Option anbietet. Setzen Sie in der Kopie die Fehlerbehandlung auf den Standard zurück, ohne Modell, Prompt, Tools oder andere Parameter zu ändern. Der Test zeigt nur, ob die Verzweigung in Ihrer Umgebung mit der Verzögerung korreliert. Er beweist nicht, dass Produktion ohne Fallback sicher ist.
Ein Nutzerbericht für Dify 1.17.1, langgenius/agent 0.0.33 und graphon 0.7.0 erfasste bei aktiviertem Fail Branch 805 message-Ereignisse zwischen 29,819 und 30,035 Sekunden, also in 0,216 Sekunden. In der Standardkontrolle verteilten sich 866 Nachrichten von 3,397 bis 40,991 Sekunden. agent_log kam währenddessen fortlaufend. Das ist Evidenz aus einer gemeldeten Umgebung, keine allgemeine Aussage und keine bestätigte Fix-Version.
Kontrolle D: nur die Answer-Reihenfolge ändern
Lassen Sie Answer zuerst ausschließlich auf den text des Agent verweisen. Fügen Sie weitere Variablen dann einzeln zurück. Steht eine späte Variable vor dem Agent-Text, kann Answer warten, obwohl Agent bereits erzeugt. Platzieren Sie die streamingfähige Textvariable früher oder nutzen Sie mehrere Answer-Knoten, die offiziell für Ausgaben in verschiedenen Phasen unterstützt werden.
Messen Sie die Ankunftszeit mit einem minimalen API-Aufruf
Rufen Sie die veröffentlichte Chatflow-API auf, statt nur die Browseranimation zu beurteilen. Der Dify-Streaming-Leitfaden definiert response_mode: "streaming" als SSE-Antwort. Bei Chatflow entsteht die sichtbare Antwort durch das geordnete Zusammenfügen der message-Ereignisse. agent_log ist ein informativer Agent-Schritt-Log; sein Empfang beweist einen aktiven Ereigniskanal, nicht die Auslieferung des Antworttexts.
Verwenden Sie für jeden Lauf Service API Base und App API Key von API Access der Testkopie. Geben Sie den vollständigen Base ein, üblicherweise einschließlich /v1; das Skript hängt /chat-messages an. Wenn Versions-Pinning verfügbar ist, tragen Sie die veröffentlichte Workflow-Versions-ID dieser Runde in DIFY_WORKFLOW_ID ein. Lassen Sie das Feld nur absichtlich leer, wenn die neueste veröffentlichte Version laufen soll. Veröffentlichen Sie in jedem Fall zuerst und nehmen Sie die Version in DIFY_RUN_LABEL auf.
{} ist für DIFY_TEST_INPUTS_JSON nur zulässig, wenn die Kopie keine verpflichtenden benutzerdefinierten Eingaben hat. Definiert Start/User Input Pflichtfelder, senden Sie in allen vier Läufen dieselben bereinigten JSON-Werte mit exakt den Parameternamen und -typen der App.
Lesen Sie im Bash den App API Key verdeckt ein und setzen Sie die Laufparameter:
unset DIFY_TARGET_NODE_ID DIFY_FALLBACK_NODE_ID DIFY_EXPECTED_FALLBACK
read -r DIFY_API_BASE && export DIFY_API_BASE
read -rs DIFY_API_KEY && export DIFY_API_KEY
read -r DIFY_TEST_QUERY && export DIFY_TEST_QUERY
read -r DIFY_RUN_LABEL && export DIFY_RUN_LABEL
read -r DIFY_WORKFLOW_ID && export DIFY_WORKFLOW_ID
read -r DIFY_TEST_INPUTS_JSON && export DIFY_TEST_INPUTS_JSON
python3 capture_dify_sse.py
Speichern Sie Folgendes als capture_dify_sse.py:
import json
import os
import sys
import time
from collections import defaultdict
from http.client import IncompleteRead
from pathlib import Path
from urllib import error, request
def redacted(value):
return None if value is None else f"<redacted:{len(str(value))} chars>"
def load_inputs():
raw = os.environ.get("DIFY_TEST_INPUTS_JSON", "{}").strip() or "{}"
try:
value = json.loads(raw)
except json.JSONDecodeError as exc:
raise SystemExit(f"DIFY_TEST_INPUTS_JSON must be valid JSON: {exc.msg}") from exc
if not isinstance(value, dict):
raise SystemExit("DIFY_TEST_INPUTS_JSON must decode to a JSON object")
return value
api_base = os.environ["DIFY_API_BASE"].rstrip("/")
api_key = os.environ["DIFY_API_KEY"]
test_query = os.environ["DIFY_TEST_QUERY"]
raw_label = os.environ.get("DIFY_RUN_LABEL", "unnamed-run").strip() or "unnamed-run"
run_label = "".join(
char if char.isalnum() or char in "-_" else "_" for char in raw_label
)[:80] or "unnamed-run"
workflow_id = os.environ.get("DIFY_WORKFLOW_ID", "").strip()
target_node_id = os.environ.get("DIFY_TARGET_NODE_ID", "").strip()
fallback_node_id = os.environ.get("DIFY_FALLBACK_NODE_ID", "").strip()
expected_fallback = os.environ.get("DIFY_EXPECTED_FALLBACK", "")
body = {
"inputs": load_inputs(),
"query": test_query,
"response_mode": "streaming",
"user": "streaming-probe",
}
if workflow_id:
body["workflow_id"] = workflow_id
req = request.Request(
f"{api_base}/chat-messages",
data=json.dumps(body).encode(),
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
},
method="POST",
)
started = time.perf_counter()
stats = defaultdict(list)
answer_parts = []
message_end_seen = False
workflow_finished_seen = False
workflow_status = None
terminal_event = None
fatal_reason = None
target_node_failed = None if not target_node_id else False
fallback_node_succeeded = None if not fallback_node_id else False
top_error = {"status": None, "code": None, "message_redacted": None}
log_path = Path(f"{run_label}-events.jsonl")
summary_path = Path(f"{run_label}-summary.json")
with log_path.open("w", encoding="utf-8") as log_file:
try:
with request.urlopen(req, timeout=180) as response:
event_name = None
for line_no, raw_line in enumerate(response, 1):
line = raw_line.decode("utf-8", errors="replace").strip()
if line.startswith("event:"):
event_name = line.split(":", 1)[1].strip()
continue
if not line.startswith("data:"):
continue
elapsed = round(time.perf_counter() - started, 3)
try:
event = json.loads(line.split(":", 1)[1].strip())
except json.JSONDecodeError:
fatal_reason = f"json_parse_error_at_stream_line_{line_no}"
record = {
"elapsed_s": elapsed,
"event": "parse_error",
"detail": "<redacted:invalid JSON payload>",
}
log_file.write(json.dumps(record, ensure_ascii=False) + "\n")
log_file.flush()
stats["parse_error"].append(elapsed)
print(f"{elapsed:8.3f}s parse_error")
break
kind = event.get("event") or event_name or "unknown"
event_name = None
data = event.get("data") if isinstance(event.get("data"), dict) else {}
answer = event.get("answer") if isinstance(event.get("answer"), str) else ""
answer_parts.append(answer)
node_id = data.get("node_id")
data_status = data.get("status")
error_status = event.get("status") if kind == "error" else None
error_code = event.get("code") if kind == "error" else None
error_message = redacted(event.get("message")) if kind == "error" else None
record = {
"elapsed_s": elapsed,
"event": kind,
"answer_chars": len(answer),
"task_id": event.get("task_id"),
"workflow_run_id": event.get("workflow_run_id"),
"node_id": node_id,
"node_title": data.get("title"),
"data_status": data_status,
"error_status": error_status,
"error_code": error_code,
"error_message_redacted": error_message,
}
log_file.write(json.dumps(record, ensure_ascii=False) + "\n")
log_file.flush()
stats[kind].append(elapsed)
if kind == "message_end":
message_end_seen = True
elif kind == "workflow_finished":
workflow_finished_seen = True
workflow_status = data_status
terminal_event = kind
elif kind == "workflow_paused":
workflow_status = "paused"
terminal_event = kind
elif kind == "error":
top_error = {
"status": error_status,
"code": error_code,
"message_redacted": error_message,
}
terminal_event = kind
if kind == "node_finished" and node_id == target_node_id:
target_node_failed = data_status in {"failed", "exception"}
if kind == "node_finished" and node_id == fallback_node_id:
fallback_node_succeeded = data_status == "succeeded"
print(
f"{elapsed:8.3f}s {kind:<22} answer_chars={len(answer)} "
f"data_status={data_status} error_status={error_status} "
f"error_code={error_code} error_message={error_message}"
)
except KeyboardInterrupt:
fatal_reason = "interrupted_by_user"
except error.HTTPError as exc:
fatal_reason = f"http_error_{exc.code}"
top_error = {
"status": exc.code,
"code": "http_error",
"message_redacted": redacted(exc.reason),
}
except error.URLError as exc:
fatal_reason = "connection_error"
top_error = {
"status": None,
"code": "connection_error",
"message_redacted": redacted(exc.reason),
}
except (IncompleteRead, TimeoutError, OSError) as exc:
fatal_reason = type(exc).__name__
top_error = {
"status": None,
"code": type(exc).__name__,
"message_redacted": redacted(exc),
}
if fatal_reason:
run_outcome, incomplete_reason = "incomplete", fatal_reason
elif workflow_status == "succeeded" and message_end_seen and workflow_finished_seen:
run_outcome, incomplete_reason = "succeeded", None
elif terminal_event == "workflow_paused":
run_outcome, incomplete_reason = "paused", None
elif workflow_status in {"failed", "partial-succeeded", "stopped"}:
run_outcome, incomplete_reason = workflow_status, None
elif terminal_event == "error":
run_outcome, incomplete_reason = "failed", None
elif workflow_finished_seen:
run_outcome = "unknown_terminal_status"
incomplete_reason = "workflow_finished_without_recognized_status"
else:
run_outcome = "incomplete"
incomplete_reason = "stream_ended_without_terminal_event"
expected_fallback_seen = (
None if not expected_fallback else expected_fallback in "".join(answer_parts)
)
complete_for_timing_acceptance = run_outcome == "succeeded"
failure_validation_requested = any(
(target_node_id, fallback_node_id, expected_fallback)
)
failure_validation_configured = all(
(target_node_id, fallback_node_id, expected_fallback)
)
failure_validation_passed = (
None
if not failure_validation_requested
else failure_validation_configured
and run_outcome in {"succeeded", "partial-succeeded"}
and all(
value is not False
for value in (
target_node_failed,
fallback_node_succeeded,
expected_fallback_seen,
)
)
)
acceptance_checks_passed = (
failure_validation_passed
if failure_validation_requested
else complete_for_timing_acceptance
)
summary = {
"run_label": run_label,
"requested_workflow_id": workflow_id or None,
"version_selection": "pinned-published-version" if workflow_id else "latest-published-version",
"run_outcome": run_outcome,
"workflow_status": workflow_status,
"terminal_event": terminal_event,
"message_end_seen": message_end_seen,
"workflow_finished_seen": workflow_finished_seen,
"incomplete_reason": incomplete_reason,
"top_level_error": top_error,
"target_node_failed": target_node_failed,
"fallback_node_succeeded": fallback_node_succeeded,
"expected_fallback_seen": expected_fallback_seen,
"complete_for_timing_acceptance": complete_for_timing_acceptance,
"failure_validation_requested": failure_validation_requested,
"failure_validation_configured": failure_validation_configured,
"failure_validation_passed": failure_validation_passed,
"acceptance_checks_passed": acceptance_checks_passed,
"event_timings": {
kind: {
"count": len(times),
"first_s": times[0],
"last_s": times[-1],
"span_s": round(times[-1] - times[0], 3),
}
for kind, times in sorted(stats.items())
},
}
summary_path.write_text(
json.dumps(summary, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
)
print(json.dumps(summary, ensure_ascii=False, indent=2))
if not acceptance_checks_passed:
sys.exit(2)
Das Skript schreibt keinen Antworttext auf die Festplatte. Es protokolliert Zeit, Node-ID/-Titel, data.status, Textlänge und bei einem error-Ereignis die Felder status und code auf oberster Ebene; message wird durch einen Längenmarker ersetzt. Es unterscheidet succeeded, failed, partial-succeeded, stopped, paused, ein oberstes error und einen Stream ohne Terminalereignis.
Dateinamen verwenden DIFY_RUN_LABEL. Bei Verbindungsabbruch oder nicht parsebarem SSE-JSON bleiben erfasste Zeiten erhalten, aber run_outcome ist incomplete und complete_for_timing_acceptance ist false. Solche Spannen dienen nur der Diagnose. Eine normale Kontrolle besteht die Zeitabnahme erst nach message_end und workflow_finished.data.status=succeeded. Auch http.client.IncompleteRead bei einem abgeschnittenen HTTP/1.1-Chunked-SSE-Stream wird abgefangen: Die Zeiten bereits empfangener Ereignisse bleiben in der Zusammenfassung, acceptance_checks_passed=false, und der in der Ausnahme enthaltene Teil des Antworttexts wird nicht ausgegeben.
Vergleichen Sie vier Zeitpunkte
- Erstes Upstream-Token. Erfassen Sie die Metrik des Providers oder eigenen Gateways, falls vorhanden. Sonst isoliert LLM → Answer den Pfad praktisch.
- Erstes SSE-Datenereignis. Ein frühes
workflow_startedbestätigt die Annahme des Laufs, nicht die fortlaufende Textzustellung. - Erstes
agent_log. Frühe Logs zeigen, dass Agent-Schritte die Verbindung passieren. Wenn Logs kommen und Text wartet, wird wahrscheinlich nicht der gesamte Kanal als ein Objekt gepuffert. - Erstes und letztes
message. Diese Zeiten bestimmen die Nutzererfahrung. Vergleichen Siefirst_s,last_sundspan_s. Viele Ereignisse in einem winzigen Intervall belegen den Schlussblock messbar.
Ein erfolgreicher Chatflow sendet message_end, danach workflow_finished mit data.status=succeeded. Der offizielle SSE-Vertrag nennt für workflow_finished.data.status die Werte succeeded, failed, partial-succeeded und stopped; erfassen Sie diese getrennt. Eine Pause für Benutzereingaben erzeugt dagegen human_input_required und anschließend das eigenständige Ereignis workflow_paused, anhand dessen das Skript run_outcome=paused festhält. Übertragen Sie die Statusaufzählung eines Ausführungsdatensatzes nicht auf den Vertrag des Ereignisses workflow_finished. Bei einem error-Ereignis liegen Status, Fehlertyp und Meldung auf oberster Ebene; das Skript zeigt status, code und eine bereinigte message. Endet die Verbindung ohne Terminalereignis oder wird Transport/JSON-Parsing unterbrochen, lautet die Zusammenfassung incomplete. Selbst mit erfassten message-Ereignissen belegt ihre Spanne keine Verbesserung, solange complete_for_timing_acceptance nicht true ist.
Wählen Sie die Änderung passend zum Ergebnis
Auch die LLM-Kontrolle wartet: Upstream zuerst beheben
Prüfen Sie Model ID, endpoint, Provider-Streaming und Zwischen-Gateways, bevor Sie zu Agent zurückkehren. Wenn BetterToken optional als OpenAI-compatible Referenz dient, empfiehlt dessen Dify-Anleitung, zuerst einen kurzen reinen Text ohne Tools zu validieren und tool calls erst nach bestätigter Unterstützung durch Modell und Endpoint zu aktivieren. Das isoliert die Modellschicht, garantiert aber keine ungepufferten Dify-Knoten.
Alle Ereignisse kommen spät: Transport untersuchen
Wiederholen Sie dieselbe Anfrage mit weniger CDN-, Reverse-Proxy- oder API-Gateway-Schichten. Streamt die direkte Dify-Verbindung fortlaufend und gruppiert ein Intermediär die Ereignisse erneut, prüfen Sie dort SSE-Weiterleitung und Buffering. Wechseln Sie nicht gleichzeitig das Modell.
Nur Fail Branch gruppiert message
Beschränken Sie den Schluss auf Ihre Dify-Version, das Agent-Plugin und die Flow-Konfiguration. Behalten Sie den Produktions-Fallback standardmäßig bei. Ist Live-Ausgabe wichtiger, entwerfen und verifizieren Sie vor einer Migration einen anderen Wiederherstellungspfad; ein Kontrolllauf rechtfertigt nicht das Löschen von Alarmen, Backup-Aufrufen oder Fehlerlogs.
Speichern Sie vor einem Dify- oder Agent-Plugin-Upgrade einen vollständigen Normallauf und eine kontrollierte Fehlerprüfung. Veröffentlichen Sie danach die Testkopie und wiederholen Sie mit demselben Prompt, Modell, bereinigten Inputs und derselben Versionsdokumentation. Eine gespeicherte Fehler-Zeitleiste allein reicht nicht: Der Zielknoten muss tatsächlich fehlschlagen, der zugehörige Fail Branch und die Fallback-Answer müssen laufen und der Benutzer muss einen bereinigten Fallback erhalten.
Answer-Umordnung stellt Streaming wieder her
Das Upstream-Modell könnte die ganze Zeit gestreamt haben, während Answer auf eine frühere Variable wartete. Behalten Sie die neue Reihenfolge oder gestufte Answer-Knoten bei und führen Sie anschließend die folgende Fehlerprüfung aus. error_type und error_message eignen sich für Routing und interne Logs; die sichtbare Answer sollte jedoch bereinigten Text statt Rohdetails enthalten.
Prüfen Sie einen kontrollierten Fehler nur in der Testkopie
Bevorzugen Sie eine vorhandene, reproduzierbare Bedingung ohne echte Benutzerdaten, bei der der exakt geprüfte Zielknoten in node_finished einen eigenen Fehler meldet; ein durch Fail Branch behandelter Fehler kann data.status=exception haben, daher darf failed nicht vorgeschrieben werden. Ein interner Toolfehler, ein agent_log-Eintrag oder Modell-Log beweist für sich allein keinen Fehler des Agent-Knotens.
Im Quellcode der entsprechenden Versionen wandelt graphon 0.7.0 einen durch FAIL_BRANCH behandelten Fehler in ein NodeRunExceptionEvent mit Status exception um. Dify 1.17.1 gibt ihn als node_finished.data.status=exception aus; ein unbehandelter Knotenfehler hat dagegen den Status failed. Das Skriptfeld target_node_failed wertet exception oder failed als Fehler des Zielknotens selbst, nicht als Fehlschlag des gesamten Workflows oder Beleg für einen erfolgreichen Fallback. Die Abnahme verlangt weiterhin den Erfolg des zugehörigen Fallback-Knotens, eine für den Benutzer sichtbare bereinigte Fallback-Antwort und eines der unten zugelassenen Endergebnisse.
Gibt es keine geeignete Bedingung, fügen Sie in einem Diagnosepfad der Testkopie vorübergehend einen HTTP-Request-Knoten ein, verwenden Sie https://fail-branch-check.invalid/ und einen kurzen Timeout. .invalid ist für garantiert ungültige Namen reserviert. Setzen Sie die Fehlerbehandlung dieses Knotens auf Fail Branch und verbinden Sie den orangefarbenen Pfad mit der Fallback-Answer. Damit prüfen Sie die Fail-Branch/Answer-Verdrahtung des temporären HTTP-Knotens; dies ersetzt keinen Agent-Level-Fehler, wenn der Agent selbst Abnahmeziel ist.
- Veröffentlichen Sie die Kopie nach der temporären Änderung. Notieren Sie veröffentlichte Version, Zielknoten-ID, Fallback-Answer-ID und eine kurze nicht sensible Phrase, die nur im Fallback vorkommt.
- Nutzen Sie Base und Key der Kopie, dieselben bereinigten Inputs und diese Version. Setzen Sie alle drei Prüfwerte und starten Sie dasselbe Skript:
read -r DIFY_TARGET_NODE_ID && export DIFY_TARGET_NODE_ID
read -r DIFY_FALLBACK_NODE_ID && export DIFY_FALLBACK_NODE_ID
read -r DIFY_EXPECTED_FALLBACK && export DIFY_EXPECTED_FALLBACK
python3 capture_dify_sse.py
- Die Zusammenfassung muss gleichzeitig
target_node_failed: true,fallback_node_succeeded: true,expected_fallback_seen: trueundfailure_validation_passed: truezeigen. Der Workflow darfsucceededoderpartial-succeededsein, jedoch nichtfailed,stopped,pausedoderincomplete. - Entfernen Sie den temporären Knoten oder stellen Sie die ursprüngliche Bedingung, Verbindungen und Konfiguration wieder her und veröffentlichen Sie erneut. Löschen Sie die drei Prüfvariablen und wiederholen Sie den Normallauf. Erst wenn dieser
complete_for_timing_acceptance: truezeigt, vergleichen Sie die erste und letztemessage-Zeit erneut.
Akzeptieren Sie den Fix erst nach zwei bestandenen Prüfungen
Akzeptieren Sie nicht allein einen schnelleren ersten Chunk. Bewahren Sie zwei Zusammenfassungen auf: einen vollständigen Normallauf mit über die Generierung verteilten message-Ereignissen und einen kontrollierten Fehler, der Zielknotenfehler, zugehörigen Fail Branch/Fallback-Answer und sichtbaren bereinigten Fallback belegt. Ein Toolfehler oder Agent-Log allein, kein Zielknotenfehler, kein sichtbarer Fallback oder kein erneuter Normallauf nach Wiederherstellung bedeutet, dass die Abnahme nicht bestanden ist.