rohitsar567 Claude Opus 4.7 (1M context) commited on
Commit
2412797
·
1 Parent(s): 171f2a4

fix(observability): KI-001..006 — log silent failures + fail-CLOSED judge

Browse files

KI-001 (P0) — Gate 4 LLM judge now fails CLOSED by default
Previously when the judge LLM call raised (rate limit, parse, network),
Gate 4 returned supported=True ("fail-open") — an unsupported claim
that should have been blocked leaked through. In BFSI this is the
wrong default. New behaviour: log the failure + return supported=False
with reason "judge_unavailable_failclosed". Override with
FAITHFULNESS_FAIL_CLOSED=0 in dev/smoke if you need the old behaviour.

KI-002 (P1) — session_state._flush() now logs disk-write failures
Was swallowing the exception silently → user's profile disappeared on
Space restart with no signal. Now logs warning with session_id + error
type so HF Space logs show flush failure rate.

KI-003 (P1) — session_state._load_from_disk() logs schema-drift failures
Was returning None silently when the on-disk JSON didn't match the
current Profile dataclass. Now logs so we can detect when schema
evolves in a way that breaks existing sessions.

KI-004 (P1) — Indic translator failure now logs
Was falling through to send Indic text to English brain silently.
Logs the language + session + error so we can tune Sarvam fallback.

KI-005 (P1) — Profile-chunk upsert failure now logs
Was swallowing Chroma write failures silently → subsequent retrieval
saw stale profile. Logs the failure so we know when Chroma is
unhappy.

KI-006 (P2) — Profile extractor LLM failure now logs
Conversational mid-chat profile updates silently disabled on extractor
failure. Now logs so we know how often the extractor flakes.

None of these changes alter functional behaviour beyond KI-001 (which
flips the default for the BFSI-correct refusal posture). The other five
add observability only — silent failures still don't crash chat, but
they now show up in HF Space logs so we can track failure rates.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

backend/faithfulness.py CHANGED
@@ -251,7 +251,19 @@ Verify."""
251
  unsupported = list(data.get("unsupported_claims", []))
252
  return supported, unsupported
253
  except Exception as e:
254
- # If judge fails, fail-open with a warning (don't block valid replies on infra hiccup)
 
 
 
 
 
 
 
 
 
 
 
 
255
  return True, [f"judge_error_failopen: {type(e).__name__}"]
256
 
257
 
 
251
  unsupported = list(data.get("unsupported_claims", []))
252
  return supported, unsupported
253
  except Exception as e:
254
+ # KI-001 BFSI compliance posture: in production we FAIL CLOSED
255
+ # (block when the judge is unavailable) so an unsupported claim
256
+ # never leaks past Gate 4 just because NIM hiccupped. Set
257
+ # FAITHFULNESS_FAIL_CLOSED=0 in dev/smoke to revert to fail-open.
258
+ import logging
259
+ import os
260
+ logging.warning(
261
+ "faithfulness gate 4 judge failure (%s: %s)",
262
+ type(e).__name__, str(e)[:200],
263
+ )
264
+ fail_closed = os.environ.get("FAITHFULNESS_FAIL_CLOSED", "1") == "1"
265
+ if fail_closed:
266
+ return False, [f"judge_unavailable_failclosed: {type(e).__name__}"]
267
  return True, [f"judge_error_failopen: {type(e).__name__}"]
268
 
269
 
backend/orchestrator.py CHANGED
@@ -145,8 +145,15 @@ async def handle_turn(
145
  translated_query = await translate_to_english(user_text)
146
  if translated_query and translated_query.strip() and translated_query != user_text:
147
  user_text = translated_query # use English for retrieval + reasoning
148
- except Exception:
149
- pass # fall through with original; if Sarvam translator fails, DeepSeek can still try
 
 
 
 
 
 
 
150
 
151
  # 1b. SESSION-STATE-AWARE FACT-FIND
152
  # Load session state. If we're already in fact-find (awaiting an answer to a
@@ -304,10 +311,23 @@ async def handle_turn(
304
  "health_conditions": session.profile.health_conditions,
305
  }
306
  await upsert_profile_chunk(session_id or "anonymous", profile_dict_for_chunk)
307
- except Exception:
308
- pass # chunk upsert failure must not block the chat
309
- except Exception:
310
- pass # extraction failure must never block the chat
 
 
 
 
 
 
 
 
 
 
 
 
 
311
 
312
  # 2. Retrieve — pass session_id so the user's profile chunk (stored in
313
  # Chroma at POST /api/profile time) gets boosted to the top of the
 
145
  translated_query = await translate_to_english(user_text)
146
  if translated_query and translated_query.strip() and translated_query != user_text:
147
  user_text = translated_query # use English for retrieval + reasoning
148
+ except Exception as e:
149
+ # KI-004 surface translator failures in HF Space logs. The
150
+ # brain will still try with the original Indic text, but with
151
+ # degraded quality. The log lets us tune the Sarvam fallback.
152
+ import logging
153
+ logging.warning(
154
+ "indic translator failed (session=%s lang=%s): %s: %s",
155
+ session_id, language, type(e).__name__, str(e)[:200],
156
+ )
157
 
158
  # 1b. SESSION-STATE-AWARE FACT-FIND
159
  # Load session state. If we're already in fact-find (awaiting an answer to a
 
311
  "health_conditions": session.profile.health_conditions,
312
  }
313
  await upsert_profile_chunk(session_id or "anonymous", profile_dict_for_chunk)
314
+ except Exception as e:
315
+ # KI-005 — log profile-chunk upsert failures so we can see
316
+ # when Chroma is locking or schema-drifting. The chat still
317
+ # ships; subsequent turns just won't see the latest profile.
318
+ import logging
319
+ logging.warning(
320
+ "profile-chunk upsert failed (session=%s): %s: %s",
321
+ session_id, type(e).__name__, str(e)[:200],
322
+ )
323
+ except Exception as e:
324
+ # KI-006 — log profile-extraction failures (extractor LLM down,
325
+ # malformed model output, etc.). The chat ships unaffected.
326
+ import logging
327
+ logging.warning(
328
+ "profile extractor failed (session=%s): %s: %s",
329
+ session_id, type(e).__name__, str(e)[:200],
330
+ )
331
 
332
  # 2. Retrieve — pass session_id so the user's profile chunk (stored in
333
  # Chroma at POST /api/profile time) gets boosted to the top of the
backend/session_state.py CHANGED
@@ -64,8 +64,14 @@ class SessionState:
64
  tmp = target.with_suffix(".json.tmp")
65
  tmp.write_text(json.dumps(payload, indent=2))
66
  tmp.replace(target)
67
- except Exception:
68
- pass # never let a disk hiccup kill the request
 
 
 
 
 
 
69
 
70
  def set_awaiting(self, question_id: Optional[str]) -> None:
71
  self.awaiting_question_id = question_id
@@ -111,7 +117,15 @@ def _load_from_disk(session_id: str) -> Optional[SessionState]:
111
  free_form_session=bool(raw.get("free_form_session", False)),
112
  last_touched=float(raw.get("last_touched", time.time())),
113
  )
114
- except Exception:
 
 
 
 
 
 
 
 
115
  return None
116
 
117
 
 
64
  tmp = target.with_suffix(".json.tmp")
65
  tmp.write_text(json.dumps(payload, indent=2))
66
  tmp.replace(target)
67
+ except Exception as e:
68
+ # KI-002 Log silent failures so HF Space logs reveal them.
69
+ # Don't crash the request — disk hiccups shouldn't kill chat.
70
+ import logging
71
+ logging.warning(
72
+ "session_state flush failed for %s: %s: %s",
73
+ self.session_id, type(e).__name__, str(e)[:200],
74
+ )
75
 
76
  def set_awaiting(self, question_id: Optional[str]) -> None:
77
  self.awaiting_question_id = question_id
 
117
  free_form_session=bool(raw.get("free_form_session", False)),
118
  last_touched=float(raw.get("last_touched", time.time())),
119
  )
120
+ except Exception as e:
121
+ # KI-003 — Log schema-drift / corrupt-JSON failures. The user will
122
+ # get a fresh session either way, but the log lets us detect when
123
+ # the Profile dataclass evolves in a way that breaks old sessions.
124
+ import logging
125
+ logging.warning(
126
+ "session_state load_from_disk failed for %s: %s: %s",
127
+ session_id, type(e).__name__, str(e)[:200],
128
+ )
129
  return None
130
 
131