Voice-agent: live retrieval web UI + region metadata filtering - #348
Voice-agent: live retrieval web UI + region metadata filtering#348HarshaNalluru wants to merge 5 commits into
Conversation
Enhances the customer-service voice-agent example with: - A Next.js web UI that shows live Moss retrieval side by side with the conversation, lighting up per turn with per-query latency. - Region metadata filtering: a US/EU picker in the UI publishes the region on a data channel; the agent scopes queries with a metadata filter (region in [selected, all]) so US and EU callers get the right policy (e.g. 30-day vs 14-day returns) from one index. - seed_index.py and data/faqs.json (20 FAQs tagged by category/region). - A demo script. The agent stays customer-facing and never mentions indexes or regions.
There was a problem hiding this comment.
Pull request overview
This PR enhances the experimental moss-live-labs/examples/voice-agent demo by adding a Next.js web UI that visualizes per-turn Moss retrieval in real time, and by introducing region-scoped retrieval via metadata filtering (US/EU) communicated over LiveKit data channels.
Changes:
- Added a Next.js web app (
web/) that joins a LiveKit room, renders the transcript, and displays retrieved chunks, scores, and latency from themoss.retrievaldata channel. - Updated the Python agent to apply a
metadata.regionfilter per query and to publish retrieval payloads to the UI; added live region selection via themoss.regiondata channel. - Added a seeded FAQ dataset (
data/faqs.json) and aseed_index.pyhelper to create the demo index, plus updated docs and a metadata-filtering demo script.
Reviewed changes
Copilot reviewed 19 out of 21 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| moss-live-labs/examples/voice-agent/agent.py | Adds region-scoped retrieval filter and publishes retrieval results to the UI; updates voice pipeline settings. |
| moss-live-labs/examples/voice-agent/seed_index.py | Seeds a Moss index with region/category-tagged FAQs. |
| moss-live-labs/examples/voice-agent/data/faqs.json | Adds sample FAQ documents with region and category metadata. |
| moss-live-labs/examples/voice-agent/README.md | Updates setup/run instructions and explains the retrieval panel wiring. |
| moss-live-labs/examples/voice-agent/DEMO_SCRIPT_METADATA.md | Adds a script focused on demonstrating metadata filtering by region. |
| moss-live-labs/examples/voice-agent/.env.example | Adds MOSS_REGION and clarifies provider key usage. |
| moss-live-labs/examples/voice-agent/web/package.json | Introduces the Next.js UI package and its dependencies. |
| moss-live-labs/examples/voice-agent/web/package-lock.json | Locks the UI dependency tree. |
| moss-live-labs/examples/voice-agent/web/tsconfig.json | TypeScript configuration for the Next.js UI. |
| moss-live-labs/examples/voice-agent/web/next.config.mjs | Next.js config for monorepo tracing root behavior. |
| moss-live-labs/examples/voice-agent/web/.gitignore | Ignores build artifacts, deps, and local env. |
| moss-live-labs/examples/voice-agent/web/.env.local.example | LiveKit env template for the UI token endpoint. |
| moss-live-labs/examples/voice-agent/web/public/moss-wordmark.svg | Adds a Moss wordmark asset for the UI header. |
| moss-live-labs/examples/voice-agent/web/lib/types.ts | Defines retrieval payload types consumed by the UI. |
| moss-live-labs/examples/voice-agent/web/components/Transcript.tsx | Renders live STT/TTS transcription turns. |
| moss-live-labs/examples/voice-agent/web/components/RetrievalPanel.tsx | Shows retrieved chunks and provides the US/EU picker publishing moss.region. |
| moss-live-labs/examples/voice-agent/web/components/AgentSide.tsx | Displays voice assistant state, visualizer, and transcript. |
| moss-live-labs/examples/voice-agent/web/app/page.tsx | UI entry: connects to LiveKit, renders agent + retrieval panel, and handles connect flow. |
| moss-live-labs/examples/voice-agent/web/app/layout.tsx | Defines app metadata and global layout/fonts/styles. |
| moss-live-labs/examples/voice-agent/web/app/globals.css | Provides UI styling and LiveKit component overrides. |
| moss-live-labs/examples/voice-agent/web/app/api/token/route.ts | Implements token minting for local demo usage (joining a random room). |
Files not reviewed (1)
- moss-live-labs/examples/voice-agent/web/package-lock.json: Generated file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- README: list Cartesia as TTS (OpenAI=LLM, Deepgram=STT), add CARTESIA_API_KEY, fix DEMO_SCRIPT link to DEMO_SCRIPT_METADATA.md. - next.config: derive dir from import.meta.url (Node 18 compatible). - agent.py: validate region from data channel against an allow-list; fix stale 'upload.py' hint to 'seed_index.py'. - seed_index.py: fail fast if Moss credentials are missing. - RetrievalPanel: add type=button + aria-pressed on the region toggle.
|
Addressed the review: README now lists Cartesia as TTS (OpenAI = LLM, Deepgram = STT) and includes |
… validates env + uses crypto.randomUUID (no hard-coded cred fallback)
Codex reviewThe PR adds a useful LiveKit/Moss demo, but the new agent has a startup-time runtime error and the demo UI can misrepresent region-scoped retrieval. There is also a dependency declaration that is contradicted by the package's own pnpm override. Findings not on changed lines:
"vite": "^8.0.6"Either update the pnpm override and lockfile to Vite 8, or keep this devDependency on the Vite 6 range actually used by the package manager. |
- Require the index to load locally (fatal on failure): region metadata filtering is ignored on cloud fallback, so a silent warning would break the demo's point. - Skip empty/interim transcriptions before querying Moss. - Validate Moss creds in entrypoint; publish_data uses payload= keyword. - Add roomName to the token response type.
…p transcript history, Node 18.18+, note unauth token route is local-dev only
|
The token endpoint is intentionally unauthenticated for local-dev (added a comment saying to gate it before any non-local deploy) — consistent with the other LiveKit examples in this repo. Resolving. |
| export async function GET() { | ||
| try { | ||
| if (!LIVEKIT_URL) throw new Error("LIVEKIT_URL is not defined"); | ||
| if (!API_KEY) throw new Error("LIVEKIT_API_KEY is not defined"); | ||
| if (!API_SECRET) throw new Error("LIVEKIT_API_SECRET is not defined"); |
| await session.start(agent=agent, room=ctx.room) | ||
|
|
||
| # Speak first, instantly — a fixed opener via say() skips the LLM round-trip. | ||
| await session.say( |
There was a problem hiding this comment.
BLOCKING session.say() is not awaitable, so the agent will raise after joining the room and before the greeting is queued.
await session.say(LiveKit defines say(...) -> SpeechHandle as a regular method, not async def; call it without await:
session.say(
"Thanks for calling Northwind support. How can I help you today?",
allow_interruptions=True,
)| key={r} | ||
| type="button" | ||
| aria-pressed={region === r} | ||
| onClick={() => setRegion(r)} |
There was a problem hiding this comment.
CONSIDER Switching regions keeps the previous retrieval visible, so after asking under US and clicking EU, the panel immediately says region: EU + global while still rendering US chunks until the next question.
onClick={() => setRegion(r)}Clear or gate the displayed results when the filter changes:
onClick={() => {
setRegion(r);
setData(null);
}}
// or render chunks only when data.region === region|
@cubic-dev-ai review this pull request |
@samanyugoyal2010 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
24 issues found across 21 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="moss-live-labs/examples/voice-agent/web/lib/types.ts">
<violation number="1" location="moss-live-labs/examples/voice-agent/web/lib/types.ts:2">
P3: Documents without an ID arrive with `id: null`, while `RetrievalDoc` promises only `string | undefined`. Modeling the emitted nullable field prevents consumers from treating a present `id` as a string.</violation>
</file>
<file name="moss-live-labs/examples/voice-agent/DEMO_SCRIPT_METADATA.md">
<violation number="1" location="moss-live-labs/examples/voice-agent/DEMO_SCRIPT_METADATA.md:4">
P3: The overview incorrectly says metadata is scoped “per line,” which can suggest a phone line or source-text line. “Scoped per query” matches the implemented per-turn `moss.query` behavior.</violation>
<violation number="2" location="moss-live-labs/examples/voice-agent/DEMO_SCRIPT_METADATA.md:16">
P3: The recording cues quote filter labels that the panel never renders, so the presenter cannot point to the stated UI text. Updating both US/EU cues to `region: US + global` and `region: EU + global` would match the demo.</violation>
</file>
<file name="moss-live-labs/examples/voice-agent/web/package.json">
<violation number="1" location="moss-live-labs/examples/voice-agent/web/package.json:9">
P2: `npm run lint` fails instead of checking the app because ESLint, `eslint-config-next`, and an ESLint configuration are absent. Adding the lint dependencies and config would make the newly exposed script usable locally and in CI.</violation>
</file>
<file name="moss-live-labs/examples/voice-agent/web/app/globals.css">
<violation number="1" location="moss-live-labs/examples/voice-agent/web/app/globals.css:7">
P2: Idle states, speaker labels, query labels, and empty-state instructions have only ~2.87:1 contrast against the panel, making this text difficult to read. Raising `--faint` to a shade with at least 4.5:1 contrast preserves the hierarchy while making these messages accessible.</violation>
<violation number="2" location="moss-live-labs/examples/voice-agent/web/app/globals.css:340">
P3: The disabled “Connecting…” button still shifts upward on hover, visually suggesting that it remains interactive. Restricting the hover rule to enabled buttons keeps the disabled state consistent.</violation>
</file>
<file name="moss-live-labs/examples/voice-agent/data/faqs.json">
<violation number="1" location="moss-live-labs/examples/voice-agent/data/faqs.json:5">
P1: EU callers asking about a manufacturing defect can receive a one-year limit even though they retain a minimum two-year legal guarantee. This policy should be split into region-specific entries, or the EU guarantee should otherwise be represented without exposing EU wording to US retrieval.</violation>
<violation number="2" location="moss-live-labs/examples/voice-agent/data/faqs.json:18">
P1: EU callers can be incorrectly told that final-sale status removes their 14-day withdrawal right; “final sale” is not itself a statutory withdrawal exception. Removing this blanket exclusion, or naming only applicable statutory exceptions, avoids presenting an unlawful policy as EU consumer law.</violation>
</file>
<file name="moss-live-labs/examples/voice-agent/web/components/AgentSide.tsx">
<violation number="1" location="moss-live-labs/examples/voice-agent/web/components/AgentSide.tsx:20">
P2: Agent connection failures and pre-connect buffering are shown to users as “idle” because the fallback collapses every unmapped `AgentState`. Preserving the actual state text would keep connection progress and failures visible.</violation>
<violation number="2" location="moss-live-labs/examples/voice-agent/web/components/AgentSide.tsx:21">
P3: A genuinely idle agent is styled as active because the `idle` flag excludes `state === "idle"`. Including that state also removes the unreachable `!state` check, since `useVoiceAssistant` always returns an `AgentState`.</violation>
<violation number="3" location="moss-live-labs/examples/voice-agent/web/components/AgentSide.tsx:26">
P3: This new component uses the deprecated `BarVisualizer.trackRef` API. Using `track` avoids introducing an API already marked for removal by the locked LiveKit version.</violation>
</file>
<file name="moss-live-labs/examples/voice-agent/web/app/api/token/route.ts">
<violation number="1" location="moss-live-labs/examples/voice-agent/web/app/api/token/route.ts:28">
P3: The issued browser token can publish camera and screen-share tracks as well as the intended microphone, so a custom client can consume unnecessary room bandwidth. Restricting `canPublishSources` to the microphone would match this demo's permission needs.</violation>
<violation number="2" location="moss-live-labs/examples/voice-agent/web/app/api/token/route.ts:35">
P3: `roomName` is returned to the browser but never used; `LiveKitRoom` only receives `serverUrl` and `participantToken`. Removing this response field and its `Conn` member would keep the new internal API contract minimal.</violation>
<violation number="3" location="moss-live-labs/examples/voice-agent/web/app/api/token/route.ts:39">
P3: Token-generation failures are returned verbatim to unauthenticated clients and never recorded server-side. Logging the exception and returning a generic response would preserve diagnostics without exposing internals.</violation>
</file>
<file name="moss-live-labs/examples/voice-agent/web/app/page.tsx">
<violation number="1" location="moss-live-labs/examples/voice-agent/web/app/page.tsx:38">
P3: The header reports `live` as soon as token fetching succeeds, before the LiveKit room is connected, so slow or failed attempts display a false status. Track `onConnected`/`onDisconnected` separately, or render status from `useConnectionState` inside the room context.</violation>
</file>
<file name="moss-live-labs/examples/voice-agent/web/components/RetrievalPanel.tsx">
<violation number="1" location="moss-live-labs/examples/voice-agent/web/components/RetrievalPanel.tsx:27">
P3: A syntactically valid but incomplete `moss.retrieval` packet crashes the panel instead of being rejected by the parse error path. Validate `query`, `docs`, each document, and `took_ms` before calling `setData`.</violation>
<violation number="2" location="moss-live-labs/examples/voice-agent/web/components/RetrievalPanel.tsx:37">
P2: A rejected region publication becomes an unhandled promise rejection and bypasses the intended error log. Attach `.catch(...)` to `publishData` (or make and await the callback) so asynchronous failures are handled.</violation>
<violation number="3" location="moss-live-labs/examples/voice-agent/web/components/RetrievalPanel.tsx:66">
P2: After switching regions, the panel can label and display US retrievals as EU (or vice versa) until another query completes, and an in-flight old result can restore the mismatch. Clear results on selection and ignore retrieval payloads whose `region` does not match the current picker value.</violation>
</file>
<file name="moss-live-labs/examples/voice-agent/web/components/Transcript.tsx">
<violation number="1" location="moss-live-labs/examples/voice-agent/web/components/Transcript.tsx:44">
P3: Screen-reader users are not notified when new transcript turns appear because this dynamic conversation has no live-region semantics. Consider keeping a persistent container with `role="log"` and `aria-live="polite"` across both empty and populated states.</violation>
<violation number="2" location="moss-live-labs/examples/voice-agent/web/components/Transcript.tsx:44">
P2: During longer calls, new turns are appended below the transcript viewport while its scroll position stays unchanged, hiding the current conversation until the user scrolls manually. Consider scrolling to the latest turn when entries are added, while preserving position when the user intentionally reviews older turns.</violation>
</file>
<file name="moss-live-labs/examples/voice-agent/agent.py">
<violation number="1" location="moss-live-labs/examples/voice-agent/agent.py:79">
P2: Retrieval payloads can report a different region from the one used for filtering if the picker changes while `moss.query` is in flight. Snapshot the region once per turn and pass that same value to both `QueryOptions` and `_publish_retrieval`.</violation>
<violation number="2" location="moss-live-labs/examples/voice-agent/agent.py:97">
P2: Empty/interim transcripts still proceed to reply generation, so the agent can speak without a user question. LiveKit's empty-turn handling uses `raise StopResponse()` here rather than calling the base hook.</violation>
<violation number="3" location="moss-live-labs/examples/voice-agent/agent.py:113">
P2: When Moss query fails, the panel keeps displaying the previous turn's documents and the LLM receives no explicit failure context. Publishing an empty/error retrieval payload and injecting the same grounded fallback in the exception path would prevent stale UI and an ungrounded reply.</violation>
<violation number="4" location="moss-live-labs/examples/voice-agent/agent.py:183">
P1: The picker can show EU while retrieval remains scoped to US when its initial/change packet arrives before this late listener is registered. A region handshake/ack or resend when the agent becomes available would keep UI and agent state synchronized.</violation>
</file>
Tip: instead of fixing issues one by one fix them all with cubic
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
Re-trigger cubic
| { | ||
| "id": "returns-policy", | ||
| "category": "returns", | ||
| "region": "all", |
There was a problem hiding this comment.
P1: EU callers asking about a manufacturing defect can receive a one-year limit even though they retain a minimum two-year legal guarantee. This policy should be split into region-specific entries, or the EU guarantee should otherwise be represented without exposing EU wording to US retrieval.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At moss-live-labs/examples/voice-agent/data/faqs.json, line 5:
<comment>EU callers asking about a manufacturing defect can receive a one-year limit even though they retain a minimum two-year legal guarantee. This policy should be split into region-specific entries, or the EU guarantee should otherwise be represented without exposing EU wording to US retrieval.</comment>
<file context>
@@ -0,0 +1,122 @@
+ {
+ "id": "returns-policy",
+ "category": "returns",
+ "region": "all",
+ "text": "Northwind accepts returns for a refund as long as items are unworn, unwashed, and in their original packaging with tags attached. Start a return from the Orders page in your account and print the prepaid label."
+ },
</file context>
| "id": "return-window-eu", | ||
| "category": "returns", | ||
| "region": "EU", | ||
| "text": "In the EU, you have a 14-day right of withdrawal starting the day you receive the item, in line with EU consumer law. Return shipping on withdrawals is free, and final-sale items are excluded." |
There was a problem hiding this comment.
P1: EU callers can be incorrectly told that final-sale status removes their 14-day withdrawal right; “final sale” is not itself a statutory withdrawal exception. Removing this blanket exclusion, or naming only applicable statutory exceptions, avoids presenting an unlawful policy as EU consumer law.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At moss-live-labs/examples/voice-agent/data/faqs.json, line 18:
<comment>EU callers can be incorrectly told that final-sale status removes their 14-day withdrawal right; “final sale” is not itself a statutory withdrawal exception. Removing this blanket exclusion, or naming only applicable statutory exceptions, avoids presenting an unlawful policy as EU consumer law.</comment>
<file context>
@@ -0,0 +1,122 @@
+ "id": "return-window-eu",
+ "category": "returns",
+ "region": "EU",
+ "text": "In the EU, you have a 14-day right of withdrawal starting the day you receive the item, in line with EU consumer law. Return shipping on withdrawals is free, and final-sale items are excluded."
+ },
+ {
</file context>
| agent = MossSemanticRetrievalAgent(moss_client, ctx.room) | ||
|
|
||
| # The UI region picker publishes { "region": "US" | "EU" } on this topic. | ||
| @ctx.room.on("data_received") |
There was a problem hiding this comment.
P1: The picker can show EU while retrieval remains scoped to US when its initial/change packet arrives before this late listener is registered. A region handshake/ack or resend when the agent becomes available would keep UI and agent state synchronized.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At moss-live-labs/examples/voice-agent/agent.py, line 183:
<comment>The picker can show EU while retrieval remains scoped to US when its initial/change packet arrives before this late listener is registered. A region handshake/ack or resend when the agent becomes available would keep UI and agent state synchronized.</comment>
<file context>
@@ -73,32 +138,68 @@ async def on_user_turn_completed(self, turn_ctx: ChatContext, new_message: ChatM
+ agent = MossSemanticRetrievalAgent(moss_client, ctx.room)
+
+ # The UI region picker publishes { "region": "US" | "EU" } on this topic.
+ @ctx.room.on("data_received")
+ def _on_data(pkt: rtc.DataPacket):
+ if pkt.topic == "moss.region":
</file context>
| "dev": "next dev", | ||
| "build": "next build", | ||
| "start": "next start", | ||
| "lint": "next lint" |
There was a problem hiding this comment.
P2: npm run lint fails instead of checking the app because ESLint, eslint-config-next, and an ESLint configuration are absent. Adding the lint dependencies and config would make the newly exposed script usable locally and in CI.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At moss-live-labs/examples/voice-agent/web/package.json, line 9:
<comment>`npm run lint` fails instead of checking the app because ESLint, `eslint-config-next`, and an ESLint configuration are absent. Adding the lint dependencies and config would make the newly exposed script usable locally and in CI.</comment>
<file context>
@@ -0,0 +1,27 @@
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "lint": "next lint"
+ },
+ "dependencies": {
</file context>
| --panel-soft: #0c110d; | ||
| --text: #f1f1f1; | ||
| --muted: #8b978c; | ||
| --faint: #5a6459; |
There was a problem hiding this comment.
P2: Idle states, speaker labels, query labels, and empty-state instructions have only ~2.87:1 contrast against the panel, making this text difficult to read. Raising --faint to a shade with at least 4.5:1 contrast preserves the hierarchy while making these messages accessible.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At moss-live-labs/examples/voice-agent/web/app/globals.css, line 7:
<comment>Idle states, speaker labels, query labels, and empty-state instructions have only ~2.87:1 contrast against the panel, making this text difficult to read. Raising `--faint` to a shade with at least 4.5:1 contrast preserves the hierarchy while making these messages accessible.</comment>
<file context>
@@ -0,0 +1,376 @@
+ --panel-soft: #0c110d;
+ --text: #f1f1f1;
+ --muted: #8b978c;
+ --faint: #5a6459;
+ --sage: #7ea97e;
+ --mint: #abe6c7;
</file context>
| { headers: { "Cache-Control": "no-store" } }, | ||
| ); | ||
| } catch (error) { | ||
| const msg = error instanceof Error ? error.message : "Unknown error"; |
There was a problem hiding this comment.
P3: Token-generation failures are returned verbatim to unauthenticated clients and never recorded server-side. Logging the exception and returning a generic response would preserve diagnostics without exposing internals.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At moss-live-labs/examples/voice-agent/web/app/api/token/route.ts, line 39:
<comment>Token-generation failures are returned verbatim to unauthenticated clients and never recorded server-side. Logging the exception and returning a generic response would preserve diagnostics without exposing internals.</comment>
<file context>
@@ -0,0 +1,42 @@
+ { headers: { "Cache-Control": "no-store" } },
+ );
+ } catch (error) {
+ const msg = error instanceof Error ? error.message : "Unknown error";
+ return new NextResponse(msg, { status: 500 });
+ }
</file context>
| const grant: VideoGrant = { | ||
| room: roomName, | ||
| roomJoin: true, | ||
| canPublish: true, // publish mic |
There was a problem hiding this comment.
P3: The issued browser token can publish camera and screen-share tracks as well as the intended microphone, so a custom client can consume unnecessary room bandwidth. Restricting canPublishSources to the microphone would match this demo's permission needs.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At moss-live-labs/examples/voice-agent/web/app/api/token/route.ts, line 28:
<comment>The issued browser token can publish camera and screen-share tracks as well as the intended microphone, so a custom client can consume unnecessary room bandwidth. Restricting `canPublishSources` to the microphone would match this demo's permission needs.</comment>
<file context>
@@ -0,0 +1,42 @@
+ const grant: VideoGrant = {
+ room: roomName,
+ roomJoin: true,
+ canPublish: true, // publish mic
+ canPublishData: true,
+ canSubscribe: true,
</file context>
| <span className="divider" /> | ||
| <span className="title">Northwind Support · voice agent</span> | ||
| </div> | ||
| <div className={`status ${conn ? "live" : ""}`}> |
There was a problem hiding this comment.
P3: The header reports live as soon as token fetching succeeds, before the LiveKit room is connected, so slow or failed attempts display a false status. Track onConnected/onDisconnected separately, or render status from useConnectionState inside the room context.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At moss-live-labs/examples/voice-agent/web/app/page.tsx, line 38:
<comment>The header reports `live` as soon as token fetching succeeds, before the LiveKit room is connected, so slow or failed attempts display a false status. Track `onConnected`/`onDisconnected` separately, or render status from `useConnectionState` inside the room context.</comment>
<file context>
@@ -0,0 +1,86 @@
+ <span className="divider" />
+ <span className="title">Northwind Support · voice agent</span>
+ </div>
+ <div className={`status ${conn ? "live" : ""}`}>
+ <span className="dot" />
+ {conn ? "live" : "offline"}
</file context>
| "moss.retrieval", | ||
| useCallback((msg: { payload: Uint8Array }) => { | ||
| try { | ||
| setData(JSON.parse(new TextDecoder().decode(msg.payload)) as RetrievalPayload); |
There was a problem hiding this comment.
P3: A syntactically valid but incomplete moss.retrieval packet crashes the panel instead of being rejected by the parse error path. Validate query, docs, each document, and took_ms before calling setData.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At moss-live-labs/examples/voice-agent/web/components/RetrievalPanel.tsx, line 27:
<comment>A syntactically valid but incomplete `moss.retrieval` packet crashes the panel instead of being rejected by the parse error path. Validate `query`, `docs`, each document, and `took_ms` before calling `setData`.</comment>
<file context>
@@ -0,0 +1,126 @@
+ "moss.retrieval",
+ useCallback((msg: { payload: Uint8Array }) => {
+ try {
+ setData(JSON.parse(new TextDecoder().decode(msg.payload)) as RetrievalPayload);
+ } catch (err) {
+ console.error("failed to parse moss.retrieval payload", err);
</file context>
| } | ||
|
|
||
| return ( | ||
| <div className="transcript"> |
There was a problem hiding this comment.
P3: Screen-reader users are not notified when new transcript turns appear because this dynamic conversation has no live-region semantics. Consider keeping a persistent container with role="log" and aria-live="polite" across both empty and populated states.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At moss-live-labs/examples/voice-agent/web/components/Transcript.tsx, line 44:
<comment>Screen-reader users are not notified when new transcript turns appear because this dynamic conversation has no live-region semantics. Consider keeping a persistent container with `role="log"` and `aria-live="polite"` across both empty and populated states.</comment>
<file context>
@@ -0,0 +1,53 @@
+ }
+
+ return (
+ <div className="transcript">
+ {ordered.map((turn) => (
+ <div className={`turn ${turn.isUser ? "user" : "agent"}`} key={turn.id}>
</file context>
What
Enhances the existing
moss-live-labs/examples/voice-agent(customer-service agent) with a web UI and a metadata-filtering feature.Live retrieval web UI (
web/)A Next.js app that renders the call next to a Moss · Knowledge Base panel, showing the documents retrieved for each turn with scores and per-query latency (
result.time_taken_ms). Data is published on amoss.retrievaldata channel.Region metadata filtering
A US / EU picker in the UI publishes the region on a
moss.regiondata channel. The agent scopes each query with a metadata filter:so a US caller and an EU caller get the right policy from the same index (e.g. 30-day vs 14-day returns), while region-agnostic FAQs (
region: all) always match. The agent stays customer-facing and never mentions indexes or regions.Contents added / changed
agent.py— per-turn retrieval, region filter, publishes results to the UIseed_index.py+data/faqs.json— 20 FAQs tagged bycategory/regionweb/— Next.js UI (retrieval panel, region picker, transcript)README.md,DEMO_SCRIPT_METADATA.mdTry it
Docs: https://docs.moss.dev/docs/integrate/metadata-filtering