Spaces:
Running
Running
Commit History
-
fix: 4 hardening fixes — sticky-retry, canned-msg copy, admin refresh wiring, recall privacy
1) sticky-retry hardening (single_brain.py / KI-singlebrain-503 extension) _gemini_call: gain `is_sticky` kwarg; non-sticky session keeps the 1-retry + 1.5s schedule (fast-fail → nim_fallback on cold-start 503); STICKY session now gets 2 retries with jittered exp backoffs (1.5s → 3s, ±25%). handle_turn reads session.single_brain_sticky once and threads it. Soaks up ~6-10s Gemini "high demand" 503 storms that previously surfaced as the sticky_graceful_retry canned reply. 2) canned-message copy (main.py) "Sorry, I'm having trouble — could you say that again?" wrongly blamed STT/comprehension when the cause was an upstream Gemini transient. New text: "My model service had a brief blip on that turn — please send the same message again, it should go through now." Plain register per feedback_no_ai_sounding_register, locates blame correctly, tells the user exactly what to do. 3) admin LLM Chain refresh wiring (frontend/public/admin/llm-control.html, KI-29…2acdc9e
-
recovery: integrate stalled-session work + de-stale/cleanup (pytest 215 green) [build-fix]
Resumes the session whose 3 background agents stalled on the 600s watchdog; captures the prior session's uncommitted #42–#110 work + data exercise plus this session's verified recovery changes. (Rebuilt from the first attempt to store frontend/public/insurer-logos/*.png via Git LFS — HF Spaces' pre-receive hook requires it; .gitattributes updated, tools/.pdf_text_cache gitignored.) Prior-session work captured (was uncommitted, behaviourally live-tested): - single_brain rewrite, full frontend rebuild, compare/scorecard/premium UI, ~77 40-data/policy_facts sourced-data edits, new tests/tools, render.yaml drop. This session's recovery (surgical, verified, behaviour-preserving): - Fix app-wide HTTP 500: a `*/` inside a CSS comment killed the stylesheet. - Remove dead get_judge_llm everywhere (judge retired in single-brain rewrite). - eval/run.py: repair imports + hard-guard against stale results.json. - Centralize ~13 hardcoded "40-data" literals to one settings.DATA_DIR (resolves id…b87bd2d -
deploy#2: unified reco-fit gate + session-privacy fix + profile-completeness + full UI rebuild
- Reco-fit gate (absorbs #10/#26): product-type + hard-eligibility(entry-age) + required-feature(maternity) + fit-rank + UIN dedup; new policy_identity.py; fixes overall_score rank-inversion. 7-persona audit failures resolved. - Profile session-persistence privacy leak closed (name-keyed directory scan removed; recall now staged + confirmation-gated, fails closed) + single_brain "Are you <name>? yes/no" confirm-prompt. - Profile-form completeness: + desired_sum_insured_inr/smoker/copay_pct/ family_medical_history; ProfileUpdateRequest silent-drop closed. - Full premium UI rebuild (front-page design system): profile panel, chat/AI replies, cited cards, marketplace, compare modal, scorecard/premium widgets; Live(BETA) real warning modal; front-page prose→bullets. - 117 backend tests pass; frontend production build clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>b134f5b
-
refactor: KI-268 — Path B cleanup: -5,219 LOC legacy + nim_fallback + chain collapse + modal wiring
CLEAN1 — nim_fallback replaces legacy orchestrator fallback (+190 LOC, NEW): - backend/nim_fallback.py: handle_turn_fallback(session, user_text, chat_history) picks NIM via llm_health.get_primary("brain") with BRAIN_CHAIN[0] last-resort. One NvidiaNimLLM.chat call with 15s timeout. Never raises — graceful TurnResult on timeout/exception. - main.py: removed `from backend.orchestrator import handle_turn`. Both fallback branches (SingleBrainError non-sticky + USE_SINGLE_BRAIN=False) now call nim_fallback.handle_turn_fallback. Sticky-graceful-retry preserved. CLEAN2 — Port _normalize_dependents into brain_tools, delete 9 legacy modules: - brain_tools._coerce_dependents now uses ported _normalize_dependents_inline + _CANONICAL_VALUES tuple. Zero imports from sales_brain_normalizer. - DELETED (5,219 LOC total): * orchestrator.py (1,832) * sales_brain_normalizer.py (855) * sales_brain.py (819) * faithfulness.py (616) * providers/tiered_brain_llm.py (257) * persona.py …55fb938 -
fix(admin): KI-267 — Health column + consolidated LLM Health header
C2 — Health column on both admin tables: - backend/llm_health.py: * ModelHealth.last_status_code: Optional[int] field (HTTP code from probe/chat failures) + back-compat default in _load_into_memory. * New helpers _classify_error_reason() + _extract_status_code() (regex with digit-boundary so Status429 / http_429 / HTTPStatusError:503 all match). * _absorb_probe_result, report_failure, report_success stamp last_status_code (cleared on success). * status_summary now emits effective_status + last_status_code + health_reason per row. Categories: "rate limit (429)" / "out of credits" / "network issue" / "service unavailable ({code})" / "auth error ({code})" / "stale" / first 40 chars fallback. - backend/admin.py: _candidate_snapshot exposes the new fields. - frontend/public/admin/llm-control.html: * NEW renderHealthCell(snap) + buildCandidatesMap(payload) helpers. * "Currently In Use" + "All Eligible Models" tables get a "Health" column. * Renders green…2689ab0 -
fix(arch+robustness): KI-225..KI-235 — Path B single-LLM rewrite + audit bundle
Architectural simplification (KI-225, Path B) — replaces the two-brain sales_brain/qa_brain handoff with a single Gemini Flash call per turn using native function-calling. Kills 40+ scaffolding failure modes at once: - No fact_find vs QA routing boundary (every confirmation/knowledge edge case in should_route_to_fact_find disappears). - No faithfulness gate rejecting natural phrasing ("yes, that sounds correct"). - No separate profile_extractor pass overwriting captured slots with null (KI-091/094 root cause permanently fixed via save_profile_field tool guard). - No rigid normalizer enum — accepts what Gemini emits, light coerce only. - No 700-token JSON contract — Gemini function-calling separates tool calls from prose, no truncation trade-off. Feature-flagged: USE_SINGLE_BRAIN=true activates the new path; default off, legacy orchestrator preserved as fallback. SingleBrainError falls back to orchestrator transparently. New files: - backend/single_brain.py — Gemini function-cal…f15911b -
fix(voice+llm+admin): KI-200/201/202/203/204/205/206/207/208/210/211 bundle
THE LAST OPERATIONAL FIX BUNDLE — voice + recommendation routing + LLM election + admin clarity. Docs cascade held for post-test confirmation. KI-200 — Admin LLM Chain redesigned to 2 clean tables. Table 1 "Currently In Use" (per use case + provider + tier). Table 2 "All Eligible Models" with explicit Primary / 1st backup / Last resort labels. Provider column color-coded (Google blue / NVIDIA green / OpenRouter orange). KI-201 — Election by chain order (not latency score). backend/llm_health.py get_primary + get_backup now walk the chain definition in priority order. Returns first eligible model. nemotron only serves as last resort. Resolves "maverick elected over qwen when all healthy" bug. KI-202 — Utterance batching with 1.5s grace. Mid-sentence pauses < 1.5s accumulate into one submission. pendingUtteranceRef + pendingChunksRef + grace timer per onend. KI-203 — Block recognition transcripts during TTS. dropResultsRef gates onresult while audio plays + 300m…e9cfc9f -
fix(admin): KI-187 — explicit Provider column (Google / NVIDIA NIM / OpenRouter)
User: "Call out what is from NIM and call out what is from OpenRouter very clearly." Previously the only way to tell NIM rows from OR rows was the ':free' suffix on the model id. For the same base model (qwen3-next-80b appears in both NIM and OR pools) this was easy to miss. Added explicit Provider column with brand-coded colors: - Google AI Studio → blue (#4285f4) - NVIDIA NIM → green (#76b900) - OpenRouter → orange (#ffa657) Column order: Model | Provider | Tier | Status | Brain Fast | Brain Main | Judge Verification: node --check on inline script — clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>897727d -
fix(voice+admin): KI-185 + KI-186 — AEC echo cancellation + 3-tier admin
KI-185 — Browser AEC + noise suppression on every getUserMedia call. User report: mic transcribing bot's own TTS audio as user input. Root cause: our getUserMedia calls used default `{audio: true}` which does NOT force AEC. Production voice agents (Zoom, Meet, ChatGPT voice) all pass explicit AEC + noiseSuppression + autoGainControl constraints. Updated both getUserMedia call sites: - frontend/src/lib/useStreamingVoice.ts (live-voice path) - frontend/src/app/page.tsx (PTT path) Effect: headphone users → near-perfect echo cancellation. Speaker users → 70-90% echo reduction. Live barge-in PRESERVED (no recognition gating on TTS playback). Sarvam STT untouched — the echo was in the Web Speech interim layer, not in Sarvam. KI-186 — Admin "What's Available" now shows all 3 tiers. User: "Both Gemini and OpenRouter need to be captured in this. Whatever is your entire LLM chain, the models you have shortlisted as being eligible, they need to be here." Previously the table only showed…792acb1 -
fix(admin): KI-184 — Refresh forces fresh probe + tier banner
User report: "refresh doesn't work." Root cause: KI-164's admin-strip removed the "Force fresh probe (slow)" button. The remaining Refresh button only called fetchLlmHealth() which re-fetches the cached probe state from /api/admin/llm-health. The backend's probe loop runs every 5 min independently. So clicking Refresh on a 2-minute-old snapshot just re-showed the same 2-minute-old data. Fix: Refresh now POSTs /api/admin/probe first (forces fresh probe), then re-fetches state, then re-renders. Same as the legacy "Force fresh probe" button used to do. Button now reads "Probing…" during the call so the user knows it's actually doing work. Plus: added a tier banner ABOVE the What's Live table that explains the 3-tier chain: Gemini Tier 0 (primary) → NIM Tier 1 (what's shown below) → OpenRouter Tier 2 → NIM Nemotron-49B last resort. This prevents confusion when the user sees nemotron showing as "primary" in the NIM table even though Gemini is actually serving traffic (per KI-179 + ADR-04…755ba01 -
feat(llm+docs): KI-177 + KI-179 + KI-183 + ADR-040 docs cascade
THREE-TIER LLM CHAIN — Google Gemini PRIMARY, NIM Tier 1, OR Tier 2. Live audit (KI-178) of OpenRouter's free-tier catalog killed my earlier recommendation: gemini-2.0-flash isn't in OR's catalog, llama-3.3-70b- :free and hermes-3-405b:free don't expose response_format. So OR's free-with-JSON pool is only nemotron-3-super-120b / qwen3-next-80b / gemma-4-31b. User obtained a Google AI Studio key, set up billing ($0 historical spend, $10 cap, free 1500/day quota active), confirmed working live on gemini-2.5-flash + gemini-2.5-flash-lite. NEW MODEL TIERS: Brain Fast (sales_brain): Tier 0 Google gemini-2.5-flash-lite (KI-183 — 2.0-flash retired) Tier 1 NIM qwen3-next-80b → mistral-large-3 → llama-4-maverick Tier 2 OR nemotron-3-super-120b:free → qwen3-next-80b:free → gemma-4-31b:free Last NIM nemotron-super-49b (only when all else dies) Brain Main (orchestrator): Tier 0 Google gemini-2.5-flash Tier 1+ same NIM/OR cascade Judg…132a829 -
fix(parser+admin+voice): KI-161 + KI-164 + KI-165 bundle
KI-161 — _parse_inr_amount no longer interprets bare digits below ₹1000 as currency, and rejects any text with age context ("29 years old", "age 25", "I am 35"). Origin: user answered the age question with "I am 29 years old" and the parser wrote ₹29 into both budget_band and income_band. 10/10 test cases pass (bare-age + real-budget). KI-164 — LLM Chain admin tab radically stripped to ONLY the two simple tables. Removed: Candidate health grid (8 cols × N rows of retired models), Recent turns table, Health snapshot card with counters, chains-grid legacy. Restructured "What's Available" Table 2 with per-role columns (Brain Fast / Brain Main / Judge) instead of free-form "Used By" text. refreshChain() now only feeds the 2-table renderer; the legacy fetch* + render* funcs are dead but intentionally left in place (their target elements no longer exist so they're never called). KI-165 — Voice/text crossfire fixed. - useLiveConversation.ts: minVoicedFrames=8 floor (~130ms), voicedFra…0a02616 -
fix(admin): KI-162 + KI-163 — simplify LLM Chain UI + truncate visitor names
KI-162 — LLM Chain admin tab redesigned to two simple tables: - "What's Live" — 3 rows (Brain Fast / Brain Main / Judge) × current model + probed-age - "What's Available" — NIM-only dedup pool with Healthy + Used By columns Dropped 124 lines of stale renderLlmHealthChain (chain cards + AVAILABLE/ UNAVAILABLE chips / PRIMARY pills). Added isRetiredModel() filter that drops any groq:/openrouter:/or: entries even if a stale payload still carries them. KI-163 — visitor table no longer blows up vertically when a session captured a multi-word garbage name (e.g. "Currently Not Having Any" pre- KI-156 Fix B). Added max-width + nowrap + ellipsis to .name-cell and .slug-cell, with hover tooltip via title= for the full value. Also deleted 4 garbage profile JSONs from 40-data/profiles/ ("Just Me" ×2, "Rohit Sara", "Not Having Any Pre-existing") that pre-dated the name stop-list fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>9ea6d75 -
fix(admin): KI-122 — clear LLM availability + in-use indicators
The admin LLM Chain panel showed a contradictory state — a chain's elected primary marked HEALTHY 100% while a red banner declared "all candidates credit-exhausted". The KI-116 reset-window fix mirrored `is_credit_eligible` but still left a hole: when one chain member had credits_reset_at=None (e.g. OpenRouter's prepaid wallet always stamps None) AND credits_remaining<=low_water, while peer NIM candidates had no credit signal yet (cold start), the banner loop registered any_signal=True on the gated member and never broke out via the permissive NIM peer. Banner fired despite the elected NIM primary being perfectly callable. Backend (`backend/admin.py::_chain_summary`): - New helpers `_candidate_available_for_calls` + `_candidate_credit_ exhausted_strict` produce per-member booleans the wire payload surfaces directly. - New strict banner rule: `chain_credit_exhausted` is true ONLY when ZERO members are available_for_calls AND at least one is strictly credit-exhausted (credits<=l…86105c9
-
feat(llm-chain): KI-087 — NIM-first election preference; non-NIM only when NIM pool empty
User architectural decision: NIM is the strategic free provider (ADR-019: single-key, $0 cost, 110+ models, no daily token cap). Groq has a hard 100K tokens/day free-tier cap that we burned through today. OpenRouter charges a real USD balance. Both should serve as **emergency fallback only**, not as primary just because their probe latency is lower. Pre-KI-087: election scored by `latency × success_rate`. Groq's 161ms LPU TTFT consistently beat NIM's 500ms-1s, so every probe round elected Groq as PRIMARY across all 3 chains. Result: every chat call hit Groq first, ate Groq's daily TPD inside 50 turns, then started returning 429s. KI-087: election now prefers ANY eligible NIM candidate over ALL non-NIM candidates. Within the NIM pool, the standard latency × success_rate score still picks the fastest healthy NIM model. Only when the NIM pool is empty (every NIM model is down, degraded, or quota-exhausted via KI-085 credit gating) does election fall through to Groq/OpenRouter as primary…d90f8c0 -
feat(admin): KI-052 — 3-tab admin panel (Profiles, Performance, LLM Chain)
Restructures the iframe-embedded admin panel into three tabs, each lazy-loaded on first activation. The existing LLM Chain functionality is preserved verbatim — original markup wrapped in <section id="tab-chain">, all the original behaviour (chain reorder + save/reload, health probe, recent usage) intact. Tab 1 — Profile + Visitor Log: • Fetches GET /api/admin/profiles (KI-048). • Sortable table — Name · Slug · First seen · Last seen · Sessions · Fields (with completeness mini-bar). Default sort: last_seen desc. Click column headers to toggle. • Tolerant of {profiles:[...]} wrapper OR bare array. • Empty state: "No named profiles yet — users haven't told the bot their name." Tab 2 — Performance: • Fetches GET /api/admin/performance (KI-048). • Three sub-cards, each collapses gracefully when its slice is null: - Gold eval: factual / citation / refusal / n_questions metric tiles + by_brain + by_type breakdown tables. - Persona audit: personas_compl…c53d167 -
feat: live time-since / time-until-next-probe counter in admin panel
The "Updated 13:11:51" label was a static wall-clock string set once at fetch time. Hard to tell at a glance how stale the snapshot is. Now the label updates every second and shows three things: Updated <wall-clock> · <Xm Ys ago> · next auto-probe in <Ym Ss> - "since" uses STATE.health.updated_at (the actual backend probe time via /api/admin/health), falling back to local fetch time if missing. - "next auto-probe in" = last-probe-time + 5min cadence (the value defined by PROBE_INTERVAL_MS, which mirrors backend/llm_health.py's background_probe_loop interval). - setInterval(renderUpdatedLabel, 1000) keeps both counters live. - Brace balance verified (281/281). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>dae8de8 -
Deploy: Stack A (NIM brain + Maverick judge + Sarvam voice). D-019.
Verified signature629dfe2 -
Deploy: Stack A (NIM brain + Maverick judge + Sarvam voice). D-019.
Verified signature13779e8