From 9c34d1e82dede05181ac235715e2518f7033c290 Mon Sep 17 00:00:00 2001 From: Michael Chihlas Date: Thu, 11 Jun 2026 15:03:15 -0400 Subject: [PATCH] =?UTF-8?q?fix(l1):=20answer=20buttons=20must=20match=20th?= =?UTF-8?q?e=20question=20=E2=80=94=20yes=5Flabel/no=5Flabel=20end-to-end?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Live walk defect: the builder generated alternatives questions ("Is Jane's account a Microsoft account or a local account?") while the UI could only offer Yes/No. Root cause: SYSTEM_PROMPT mandated a label-less '' shape with no way to express the two answers. - SYSTEM_PROMPT: question nodes must carry yes_label/no_label — the literal button texts; alternatives questions must use the alternatives as labels. - validate_node: labels hard-floor-scanned, must be distinct non-empty strings. - _ensure_labels: server defaults missing labels to Yes/No. - advance_ai_build: records answer_label (and both labels) in walked_path, derived from the server-held pending_node — never client-supplied. - _build_context: LLM context shows the chosen label, not a bare yes/no (a raw "-> yes" on an alternatives question degrades the next generation). - normalize_walked_path: captured flywheel trees keep question labels. - Frontend: buttons render yes_label/no_label; walk transcript and L1EscalationsSection render answer_label. Phase 2A backend set: 137 passed / 0 failed / 8 deselected. tsc, eslint, vite build clean. Co-Authored-By: Claude Fable 5 --- backend/app/services/ai_tree_builder.py | 44 ++++++++++- backend/app/services/l1_session_service.py | 18 +++++ backend/tests/test_ai_tree_builder.py | 77 +++++++++++++++++++ backend/tests/test_l1_session_service.py | 35 +++++++++ .../components/l1/L1EscalationsSection.tsx | 2 +- .../src/components/l1/L1WalkTreeVariant.tsx | 6 +- frontend/src/types/l1.ts | 9 ++- 7 files changed, 182 insertions(+), 9 deletions(-) diff --git a/backend/app/services/ai_tree_builder.py b/backend/app/services/ai_tree_builder.py index 59a27a7d..e743de3f 100644 --- a/backend/app/services/ai_tree_builder.py +++ b/backend/app/services/ai_tree_builder.py @@ -38,11 +38,19 @@ HARD RULES: - When you run out of safe in-scope steps, DO NOT GUESS. Emit an "escalate" node. Return ONLY a JSON object for ONE node, one of: -{"node_type":"question","text":""} +{"node_type":"question","text":"","yes_label":" @@ -252,7 +252,7 @@ export function L1WalkTreeVariant({ session, onSessionUpdate, onDone }: Props) { {session.walked_path.map((step, i) => (
  • {step.question ?? step.text} - {step.answer && → {step.answer}} + {step.answer && → {step.answer_label ?? step.answer}} {step.l1_note && {step.l1_note}}
  • ))} diff --git a/frontend/src/types/l1.ts b/frontend/src/types/l1.ts index 69fa25e5..d6665e95 100644 --- a/frontend/src/types/l1.ts +++ b/frontend/src/types/l1.ts @@ -12,6 +12,8 @@ export interface WalkStep { question?: string text?: string answer: string | null + /** Button text the tech clicked (ai_build); falls back to `answer`. */ + answer_label?: string l1_note: string | null } @@ -75,9 +77,12 @@ export interface IntakeResult { category?: string // for 'out_of_scope' } -/** A single node of an AI-built decision tree, returned by /next-node. */ +/** A single node of an AI-built decision tree, returned by /next-node. + * Question nodes carry the literal button texts (yes_label/no_label) so the + * choices always match the question ("Microsoft account" / "Local account", + * not a mismatched Yes/No). The backend defaults them to Yes/No. */ export type TreeNode = - | { node_type: 'question'; id: string; text: string } + | { node_type: 'question'; id: string; text: string; yes_label?: string; no_label?: string } | { node_type: 'instruction'; id: string; text: string } | { node_type: 'resolved'; id: string; text: string } | { node_type: 'escalate'; id: string; reason_category?: string; text: string }