72 lines
2.6 KiB
Python
72 lines
2.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Fail-closed marker for a dead research lane of the deep-research graph.
|
|
|
|
Reached ONLY via research_one_question's `fallback:` route inside the
|
|
`research_each_question` map branch (branch-local — a map branch's fallback
|
|
must stay inside the branch subgraph). Grounded in engine behavior: on
|
|
branch failure the engine writes its failure text into `finding` via the
|
|
node's state_updates BEFORE routing to the fallback — "Agent node failed:
|
|
<chain>" now that the branch spawns a librarian agent, "LLM node …failed:
|
|
<chain>" for an llm branch (on success the same key holds the researched
|
|
findings, which never start with either prefix). This node normalizes the failure text into a
|
|
PIPELINE-FAULT finding and the chain ends here — the map collects the fault
|
|
as the lane's `finding`, so one dead research lane never sinks the whole
|
|
map. combine_findings later lifts PIPELINE-FAULT findings into
|
|
`pipeline_faults` so the fault also surfaces in the report's Pipeline notes.
|
|
The question text is part of the fault so distinct dead lanes with identical
|
|
engine failure text do not collapse into one entry there.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
|
|
MAX_DETAIL_CHARS = 500
|
|
MAX_QUESTION_CHARS = 200
|
|
|
|
|
|
def load_state():
|
|
if path := os.environ.get("GRAPH_STATE_FILE"):
|
|
with open(path) as f:
|
|
return json.load(f)
|
|
return json.loads(os.environ.get("GRAPH_STATE", "{}"))
|
|
|
|
|
|
def fault_finding(question, detail):
|
|
return {
|
|
"finding": (
|
|
"PIPELINE-FAULT: question research failed — finding unavailable — "
|
|
f'{detail} (question: "{question}")'
|
|
)
|
|
}
|
|
|
|
|
|
def question_label(state):
|
|
raw = state.get("question")
|
|
question = raw.strip().replace("\n", " ") if isinstance(raw, str) else ""
|
|
if not question:
|
|
return "(unknown question)"
|
|
if len(question) > MAX_QUESTION_CHARS:
|
|
question = question[: MAX_QUESTION_CHARS - 1] + "…"
|
|
return question
|
|
|
|
|
|
def main():
|
|
state = load_state()
|
|
raw = state.get("finding")
|
|
detail = raw.strip().replace("\n", " ") if isinstance(raw, str) else ""
|
|
if not detail.startswith(("LLM node", "Agent node")):
|
|
detail = "the research lane died without recording a failure detail"
|
|
if len(detail) > MAX_DETAIL_CHARS:
|
|
detail = detail[: MAX_DETAIL_CHARS - 1] + "…"
|
|
print(json.dumps(fault_finding(question_label(state), detail)))
|
|
|
|
|
|
try:
|
|
main()
|
|
except Exception as e: # noqa: BLE001 — the fault marker itself must never crash
|
|
print(
|
|
json.dumps(
|
|
fault_finding("(unknown question)", f"fault-marker script error: {e}")
|
|
)
|
|
)
|