Ask Phoenix — Public Knowledge-Base Chat Assistant
Audiences: investor, client, partner, developer
Ask Phoenix is the public-facing chat surface that lets anyone — investor, client, partner, developer — query the Dudoxx HMS knowledge base in natural English / German / French and get cited answers grounded in the 12-layer technical corpus. Available as a floating side panel on every
/docs/[layer]/[topic]page and as a full-page chat at/docs/ask.
Business Purpose
The HMS knowledge base contains ~100 topic pages spread over 12 architectural layers. Static documentation works for users who already know what to look for — but most prospects, clients, and partners do not. Ask Phoenix lowers the discovery cost from "browse 12 sections" to "type a question". It produces answers with clickable citation chips that deep-link to the exact KB topic + heading the claim came from — so the chat surface accelerates discovery without replacing the structured docs.
Three concrete use cases:
- Investor due diligence: "How does FHIR partitioning work?" returns a
short grounded answer with chips into
[02-data-and-fhir/fhir-partitions-tenancy]. - Partner onboarding: "What components must I change to add a new TUCAN tool?" returns multi-step guidance with chips into ai-tucan layer topics.
- Sales conversations: "Why does ddx-web never talk to HAPI FHIR directly?"
returns the security rationale with chips into
[03-security-and-tenants/...].
Audiences
- Investor: Ask Phoenix is the public demo of the Dudoxx KB. Try it at
https://<host>/en/docs/ask. Every answer cites its sources — the assistant cannot fabricate. Refusal-class questions ("What is OpenAI's pricing for GPT-5?", "What is Dudoxx's revenue?") are politely declined, not hallucinated. - Client / partner: Self-service onboarding. Skim a KB topic, click the Phoenix launcher in the page chrome, ask a follow-up question. The panel preserves chat state across topic navigations within the same session.
- Developer: Ask Phoenix shares the same Mastra agent runtime
(
streamPhoenixinddx-web/src/mastra/agents/phoenix.ts:84) as the internal TUCAN system. To extend the assistant's knowledge, ingest new topic chunks into thedudoxx_kb_techdocsQdrant collection; the chat surface picks them up immediately — no front-end change needed.
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Public KB surfaces │
│ │
│ /[locale]/docs/[layer]/[topic] → topic reader page │
│ ↳ KbShellPhoenixLauncher button in page chrome │
│ ↳ AskPhoenixPanel (createPortal floating drawer) │
│ │
│ /[locale]/docs/ask → full-page chat surface │
│ ↳ PhoenixFullPageChat (centered chat column) │
└─────────────────────────────────────────────────────────────┘
│
│ AI SDK v6 useChat with DefaultChatTransport
│
▼
┌─────────────────────────────────────────────────────────────┐
│ /api/phoenix/chat (POST) │
│ ddx-web/src/app/api/phoenix/chat/route.ts:21 │
│ · validate request │
│ · streamPhoenix({ messages, env }) → Response (SSE) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Mastra agent: Phoenix │
│ See [[phoenix-agent-architecture]] for full internals │
│ · model: dudoxx-gemma, temp 0.3, enable_thinking: false │
│ · tools: kb-plan (private), kb-search (cited) │
│ · stopWhen: steps.length >= 8 │
└─────────────────────────────────────────────────────────────┘
UI surfaces
AskPhoenixPanel (ddx-web/src/components/kb/AskPhoenixPanel.tsx:37)
- Floating side panel mounted via
createPortal. - Triggered by
KbShellPhoenixLauncherbutton in the topic reader chrome. - Shows "Planning…" chip when the agent is in the hidden kb-plan step
(
PhoenixToolPartshort-circuitstool-kb-planto a chip-only render). - Shows expandable "Searching…" chip for each kb-search call.
- Renders the final answer via
PhoenixAnswer→MarkdownRenderer(same chrome as topic pages — code/mermaid/admonitions look identical). - "Open full page" link routes to
/docs/askpreserving the conversation state via search params.
PhoenixFullPageChat (ddx-web/src/components/kb/PhoenixFullPageChat.tsx:34)
- Centered chat column (max-w-prose) for distraction-free reading.
- Hero header instead of drawer chrome.
- Routed at
/[locale]/docs/askviaddx-web/src/app/[locale]/docs/ask/page.tsx:27. - Same Mastra agent backend as the side panel — only the layout differs.
PhoenixToolPart (ddx-web/src/components/kb/PhoenixToolPart.tsx:42)
- Renders each tool-call step in the chat transcript.
case 'kb-plan'→ chip-only render witht('kb.phoenix.planning')(i18n keys insrc/locales/{en,de,fr}/kb.json: "Planning…" / "Plane…" / "Planification…"). Never expands kb-plan args into the visible message — that would leak the hidden plan and inflateassistant_textlength (AC-10 invariant).case 'kb-search'→ expandable chip showing the query and top hits.
PhoenixAnswer (ddx-web/src/components/kb/PhoenixAnswer.tsx:67)
- Markdown renderer for the assistant message.
- Detects
[layer/slug]citation chips and converts them into clickable<Link>elements pointing to/[locale]/docs/[layer]/[slug]. - Reuses
MarkdownRendererfrom the topic-page surface so code blocks, admonitions, and tables share visual chrome.
Tech Stack & Choices
| Layer | Choice | Rationale |
|---|---|---|
| Client streaming | AI SDK v6 useChat + DefaultChatTransport | Auto-handles SSE reconnect, message ordering, tool-call streaming |
| API route | Next.js 16 route handler (/api/phoenix/chat) | Lives in ddx-web (port 6200). Browser never talks to ddx-api here — the Mastra agent runs in Next.js |
| Agent backend | Mastra agent in ddx-web (src/mastra/agents/phoenix.ts) | Same Mastra runtime as TUCAN — one source of truth for AI orchestration |
| Vector store | Qdrant dudoxx_kb_techdocs | Curated technical-docs collection. Re-ingested from ddx-presentation/knowledge-base/ content |
| Markdown | MarkdownRenderer reused from topic-page surface | Visual consistency between static docs and chat answers |
| i18n | next-intl with kb.* namespace | EN/DE/FR all carry kb.phoenix.planning, etc. Locales in lockstep |
| Citations | [layer/slug] chip syntax detected client-side | Lightweight — no per-token parsing required. The agent emits chips verbatim from kb-search results |
Why a side panel AND a full-page chat?
Side panel preserves topic context (the user keeps reading the docs while
asking follow-up questions). Full-page chat is the discovery entrypoint —
linked from the landing page / sales materials, no topic context required.
Both share the same Mastra agent and the same /api/phoenix/chat route, so
there is one place to fix any Phoenix behaviour.
Data Flow
1. User opens /en/docs/02-data-and-fhir/fhir-partitions-tenancy.
2. Clicks KbShellPhoenixLauncher → AskPhoenixPanel mounts via createPortal.
3. User types "How does the multi-tenant data isolation work?"
4. useChat POSTs to /api/phoenix/chat with messages array.
5. Server-side: streamPhoenix → first step is the hidden kb-plan tool call:
{ question: '...', sub_questions: ['How is FHIR partitioning implemented?',
'How is data isolated between tenants in HAPI FHIR?'] }
- PhoenixToolPart receives a tool-kb-plan stream event → renders chip
'Planning…' (or 'Plane…' / 'Planification…' depending on locale).
6. Then 2 kb-search calls fire — one per sub_question:
- PhoenixToolPart renders 'Searching…' chips with expandable detail.
7. Final answer streams in as text-delta chunks:
- PhoenixAnswer renders the markdown, converts [02-data-and-fhir/fhir-...]
into clickable links to the actual topic pages.
8. User clicks a citation chip → navigates to the cited topic page.
9. The side panel persists; the user can continue the conversation about
the new topic with full context.
Implicated Code
Mandatory ≥3 file:line citations.
ddx-web/src/app/[locale]/docs/ask/page.tsx:27—PhoenixAskPageserver component. Builds metadata viagenerateMetadata(line 16), renders<PhoenixFullPageChat />client component.ddx-web/src/components/kb/PhoenixFullPageChat.tsx:34—export function PhoenixFullPageChat(). WiresuseChat({ api: '/api/phoenix/chat', transport: new DefaultChatTransport() }), renders header + chat column + composer.ddx-web/src/components/kb/AskPhoenixPanel.tsx:37—export function AskPhoenixPanel(...). Side-panel variant usingcreatePortalto mount outside the topic-page tree.ddx-web/src/components/kb/PhoenixToolPart.tsx:42—export function PhoenixToolPart({...}). The function-level branch onpart.type === 'tool-kb-plan'returns a chip-only fragment;tool-kb-searchrenders an expandable detail. Critical for AC-10 (no sub-question leak into visible answer).ddx-web/src/components/kb/PhoenixAnswer.tsx:67—export function PhoenixAnswer({ text }). Markdown rendering with citation chip detection + deep-link conversion.ddx-web/src/app/api/phoenix/chat/route.ts:21—POSThandler. Builds env, callsstreamPhoenix, returns the streamed SSE response.ddx-web/src/locales/en/kb.json(+ de, fr) — i18n keyphoenix.planning= "Planning…" / "Plane…" / "Planification…". All three locales in lockstep.
Operational Notes
Public route exposure
/[locale]/docs/ask is a public route — no auth required. The Mastra
agent runs server-side in ddx-web (Next.js 16 route handler), and the
Qdrant query happens server-side too. The browser never talks to Qdrant or
the LLM gateway directly. This means:
- Anyone can use Ask Phoenix without signup.
- Rate limiting must live in the
/api/phoenix/chatroute (currently inherits Next.js platform-level limits — consider explicit per-IP throttling if traffic grows). - No PII should ever appear in user questions (the route does not strip PII before passing to the LLM). Document this in the privacy note on the landing page.
Pitfalls
- Citation chips that don't deep-link:
[layer/slug]chips MUST use the raw slug from the kb-search result (layer: 'infrastructure',slug: 'infra-postgres'). Inventing prefixes like[01-infrastructure/infra-postgres]breaks the markdown chip detection. The Phoenix system prompt forbids invented prefixes; verify by spot-checking the eval report. - Locale fallback gotcha: if a user's browser is
it-ITand the route lacks an Italian locale, next-intl falls back toen. The Mastra agent detects the language from the user's typed message regardless — so a user typing in French on an/en/docs/askroute gets a French answer. This is by design (the agent's output-language-strict rule). - Tool-call chips can flash on slow networks: if the SSE stream stutters between the kb-plan event and the first kb-search event, users see "Planning…" alone for ~1 second. This is acceptable UX (clearly conveys "working") but consider a smoother transition if feedback flags it.
- No conversation persistence: each
useChatinstance keeps history in React state only. Refresh = new conversation. Persisting to backend would require anai-chat-sessions-style record but is OUT of scope for the public surface.
Validation
# Build sanity cd ddx-web && pnpm build # i18n keys present in all 3 locales for L in en de fr; do jq -r '.phoenix.planning // "MISSING"' ddx-web/src/locales/$L/kb.json done # Type + lint Phoenix UI surface pnpm tsc --noEmit pnpm eslint src/components/kb/*.tsx src/app/[locale]/docs/ask/page.tsx
Common defects
- "Planning…" chip never disappears → kb-plan tool returned ok:false on first call and the agent stopped. Check the Mastra agent prompt's rule 3 ("retry kb-plan exactly once on ok:false using suggestion").
- Empty answer with 0 citations → falls into the Q6/Q8 anti-loop class. Verify the tool-layer guard is wired (see [[phoenix-agent-architecture]] Operational Notes).
- Citation chip 404s on click → the slug in the chip doesn't match
any KB topic page. Cause: a kb-search hit whose
slugfield is stale or the chunk was ingested without a matching markdown page inddx-presentation/knowledge-base/. Re-run the inventory check.