Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ CTX_REDIS_INSTANCE_ID=
DEMO_USER_ID=
DEMO_USER_NAME=
DEMO_USER_EMAIL=
# Available domains: reddash, electrohub, healthcare
# Available domains: reddash, electrohub, finance-researcher, healthcare, radish-bank
DEMO_DOMAIN=reddash

# ═══════════════════════════════════════════════════════
Expand All @@ -44,3 +44,8 @@ REDIS_INSTANCE_NAME="Reddash Redis Cloud"
CTX_FORCE_CREATE=false
BACKEND_PORT=8040
FRONTEND_PORT=3040

# Radish Bank input router: Hugging Face model for RedisVL SemanticRouter (downloads on first use)
# RADISH_HF_EMBEDDING_MODEL=sentence-transformers/all-mpnet-base-v2
# Optional: https://huggingface.co/settings/tokens — higher Hub limits / private models
# HF_TOKEN=
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ help:
@echo " make cache-domain-price-csvs DOMAIN=<domain> Run a domain-local price cache script when available"
@echo " Optional: EXTRA_ENV_FILE=/path/to/shared.env"
@echo " make flush-redis Flush the Redis database (FLUSHDB)"
@echo " make reset Flush Redis + recreate surface + reload data"
@echo " make reset [DOMAIN=...] Flush Redis + recreate surface + reload data (DOMAIN defaults to reddash)"
@echo " make backend Start FastAPI backend"
@echo " make frontend Start Vite frontend"
@echo " make dev Run backend and frontend together"
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ The repo currently includes built-in demo domains for:
- `electrohub` — electronics retail and order support
- `finance-researcher` — ShiftIQ watchlist research across filings, metrics, prices, and live updates
- `healthcare` — RedHealthConnect patient success portal (appointments, referrals, providers)
- `radish-bank` — Radish Bank retail demo (accounts, FDs, insurance, branches, fee waivers + policy docs)

**Two modes, same UI:**

Expand Down Expand Up @@ -132,6 +133,9 @@ Open http://localhost:3040 and try:
- In `healthcare`:
- *"Do I have any upcoming appointments?"*
- *"Find me a cardiologist accepting new patients?"*
- In `radish-bank`:
- *"What accounts do I have and what are my balances?"*
- *"Place 2000 SGD into the 6-month fixed deposit from my savings account."*

---

Expand Down Expand Up @@ -189,6 +193,8 @@ The `healthcare` domain models a patient success portal with six entity types:

Healthcare schema definitions live in [`domains/healthcare/schema.py`](domains/healthcare/schema.py).

The `radish-bank` domain is a compact retail-banking demo (single customer): accounts, cards, FD and insurance catalogs, branches with hours, holdings, service requests, and **BankDocument** rows for vector RAG. Schema lives in [`domains/radish-bank/schema.py`](domains/radish-bank/schema.py).

---

## Demo Paths
Expand All @@ -199,6 +205,7 @@ See:
- [`domains/electrohub/docs/demo_paths.md`](domains/electrohub/docs/demo_paths.md)
- [`domains/finance-researcher/docs/demo_paths.md`](domains/finance-researcher/docs/demo_paths.md)
- [`domains/healthcare/docs/demo_paths.md`](domains/healthcare/docs/demo_paths.md)
- [`domains/radish-bank/docs/demo_paths.md`](domains/radish-bank/docs/demo_paths.md)

Reddash includes four scripted conversation flows:

Expand Down Expand Up @@ -267,7 +274,8 @@ context-engine-demos/
│ ├── reddash/ # Delivery-support reference domain
│ ├── electrohub/ # Electronics retail reference domain
│ ├── finance-researcher/ # ShiftIQ watchlist research domain
│ └── healthcare/ # Patient success portal domain
│ ├── healthcare/ # Patient success portal domain
│ └── radish-bank/ # Retail banking + policy-doc RAG demo
│ ├── domain.py # DOMAIN export implementing the contract
│ ├── schema.py # Entity definitions
│ ├── prompt.py # Domain prompt/playbooks
Expand Down
50 changes: 48 additions & 2 deletions backend/app/main.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from __future__ import annotations

import asyncio
import base64
import json
from contextlib import asynccontextmanager
from pathlib import Path
from time import perf_counter
from typing import Any, AsyncIterator
Expand All @@ -25,7 +27,23 @@
settings = get_settings()
domain = get_active_domain(settings)
ROOT_DIR = Path(__file__).resolve().parents[2]
app = FastAPI(title=f"{domain.manifest.branding.app_name} Demo")

# Avoid importing sentence-transformers / PyTorch unless this process serves Radish Bank.
if settings.demo_domain == "radish-bank":
from backend.app.radish_input_router import make_radish_bank_lifespan

_lifespan = make_radish_bank_lifespan(settings)
else:

@asynccontextmanager
async def _lifespan(_app: object):
yield


app = FastAPI(
title=f"{domain.manifest.branding.app_name} Demo",
lifespan=_lifespan,
)
app.add_middleware(
CORSMiddleware,
allow_origins=[settings.cors_origin, "http://localhost:3040", "http://127.0.0.1:3040"],
Expand Down Expand Up @@ -164,13 +182,13 @@ async def domain_config() -> JSONResponse:

async def cs_event_stream(request: ChatRequest) -> AsyncIterator[str]:
timer = Timer()
latest_message = request.messages[-1].content if request.messages else ""
yield format_sse_event("status", text="Initializing agent…", ts=timer.elapsed_ms())

agent = await get_agent()
defer_final_answer = runtime_config.get("enable_post_model_verifier", False)

thread_id = request.thread_id or "default"
latest_message = request.messages[-1].content if request.messages else ""

config = {"configurable": {"thread_id": thread_id}}

Expand Down Expand Up @@ -328,6 +346,34 @@ async def rag_event_stream(question: str) -> AsyncIterator[str]:
async def chat_stream(request: ChatRequest) -> StreamingResponse:
question = request.messages[-1].content if request.messages else ""

if settings.demo_domain == "radish-bank":
from backend.app.radish_input_router import (
RadishRouterUnavailableError,
get_radish_router,
radish_blocked_sse,
radish_router_unavailable_sse,
)

router = get_radish_router(settings)
try:
label, _ = await asyncio.to_thread(router.classify, question)
except RadishRouterUnavailableError:
return StreamingResponse(
radish_router_unavailable_sse(),
media_type="text/event-stream",
status_code=503,
)
if label == "malicious":
return StreamingResponse(
radish_blocked_sse(malicious=True),
media_type="text/event-stream",
)
if label == "off_topic":
return StreamingResponse(
radish_blocked_sse(malicious=False),
media_type="text/event-stream",
)

if request.mode == "simple_rag":
return StreamingResponse(rag_event_stream(question), media_type="text/event-stream")

Expand Down
Loading