Add travel-concierge example (cloud catalog + live session)#347
Add travel-concierge example (cloud catalog + live session)#347HarshaNalluru wants to merge 6 commits into
Conversation
… session) A voice travel concierge that answers from two Moss indexes at once: - a pre-loaded catalog (long-term, shared across every call) - a live session that captures what the traveler says on this call Each turn it recalls stated preferences from the session and recommends trips from the catalog. Facts are distilled from each turn before they are stored, so the session holds clean preferences rather than raw questions. The web UI shows both indexes side by side, lighting up per turn with per-query latency. Includes agent.py, seed_index.py, a Next.js web UI, README, and a demo script.
There was a problem hiding this comment.
Pull request overview
Adds a new experimental travel-concierge voice-agent example under moss-live-labs/ that demonstrates querying two Moss indexes in the same LiveKit call (pre-seeded cloud catalog + per-call live session memory), with a Next.js UI that visualizes both retrieval result sets side-by-side.
Changes:
- Introduces a Python LiveKit agent that queries a pre-loaded catalog index and a per-call session index, publishing retrieval payloads over a LiveKit data channel.
- Adds a Next.js web UI (LiveKit Components) to connect to the room, render transcript, and render dual retrieval panels.
- Adds supporting assets/docs/config (catalog seed script, env examples, demo script, lockfiles, styling).
Reviewed changes
Copilot reviewed 21 out of 24 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| moss-live-labs/examples/travel-concierge/web/tsconfig.json | TypeScript config for the Next.js UI. |
| moss-live-labs/examples/travel-concierge/web/public/moss-wordmark.svg | Adds branding asset used by the UI header. |
| moss-live-labs/examples/travel-concierge/web/package.json | Defines Next.js + LiveKit UI dependencies and scripts. |
| moss-live-labs/examples/travel-concierge/web/package-lock.json | Locks JS dependency versions for reproducible installs. |
| moss-live-labs/examples/travel-concierge/web/next.config.mjs | Next.js configuration for monorepo tracing root (and should handle LiveKit server SDK externals). |
| moss-live-labs/examples/travel-concierge/web/lib/types.ts | Shared types for retrieval payload and docs rendered in the UI. |
| moss-live-labs/examples/travel-concierge/web/components/Transcript.tsx | Renders live STT/TTS transcript from LiveKit transcriptions. |
| moss-live-labs/examples/travel-concierge/web/components/DualPanel.tsx | Subscribes to moss.retrieval data channel and renders catalog vs session hits. |
| moss-live-labs/examples/travel-concierge/web/components/AgentSide.tsx | Agent “orb”, state label, transcript, and control bar UI. |
| moss-live-labs/examples/travel-concierge/web/app/page.tsx | Main page: connect flow and LiveKitRoom layout wiring. |
| moss-live-labs/examples/travel-concierge/web/app/layout.tsx | App metadata, fonts, LiveKit styles, global CSS inclusion. |
| moss-live-labs/examples/travel-concierge/web/app/globals.css | Styling for split layout and retrieval panels. |
| moss-live-labs/examples/travel-concierge/web/app/api/token/route.ts | API route that mints a LiveKit participant token for local dev. |
| moss-live-labs/examples/travel-concierge/web/.gitignore | Ignores Next build output, node_modules, and env files. |
| moss-live-labs/examples/travel-concierge/web/.env.local.example | Example LiveKit env vars for the UI token route. |
| moss-live-labs/examples/travel-concierge/seed_index.py | Seeds the cloud catalog index from data/catalog.json. |
| moss-live-labs/examples/travel-concierge/README.md | Setup and explanation for the demo (catalog + session). |
| moss-live-labs/examples/travel-concierge/pyproject.toml | Python deps for the agent + seeding scripts. |
| moss-live-labs/examples/travel-concierge/DEMO_SCRIPT.md | Suggested narration/script for recording a demo. |
| moss-live-labs/examples/travel-concierge/data/catalog.json | Destination catalog content used to seed the cloud index. |
| moss-live-labs/examples/travel-concierge/agent.py | LiveKit agent: queries session + catalog, publishes retrieval payloads, distills user facts into session memory. |
| moss-live-labs/examples/travel-concierge/.python-version | Pins Python version for the example. |
| moss-live-labs/examples/travel-concierge/.env.example | Example env vars for Moss + providers + LiveKit. |
Files not reviewed (1)
- moss-live-labs/examples/travel-concierge/web/package-lock.json: Generated file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- next.config: use serverExternalPackages for livekit-server-sdk instead of a non-standard outputFileTracingRoot, matching insurance-adjuster - token route: require LIVEKIT_* env vars and return 500 on misconfig instead of silently falling back to well-known dev credentials - DualPanel: reuse a single TextDecoder across data-channel messages
Codex reviewThe PR mostly adds a self-contained travel-concierge demo, but there are a few correctness and package metadata issues worth fixing. The most important risks are inconsistent dependency resolution and brittle handling of extracted session facts. Findings not on changed lines:
"vite": "^8.0.6"
"license": "Proprietary"
|
- token route: use crypto.randomUUID() for room/identity (collision-resistant). - agent.py: await the previous turn's fact-remember task before recalling, so an immediate follow-up question sees the just-stored facts (no fire-and-forget race).
|
Addressed the review: token route now uses The other three notes were already handled in the current code: the token route validates |
…seed_index), add uuid suffix to session name (no same-second collision), Node 18.18+ in README
…CRIPT quote block; fail fast if catalog index is missing
|
On injecting retrieved catalog rows + remembered facts into the model context: that's the intended RAG grounding for this demo (the agent is instructed to use it to help the traveler). Noted for anyone hardening it for production; resolving. |
…; add roomName to token response type
| "devDependencies": { | ||
| "@types/node": "^22", | ||
| "@types/react": "^19", | ||
| "@types/react-dom": "^19", | ||
| "typescript": "^5" | ||
| } |
| @@ -0,0 +1,13 @@ | |||
| export type Doc = { | |||
| id?: string; | |||
| # 0. Make sure the previous turn's facts are stored before we recall, | ||
| # so an immediate follow-up question sees them. | ||
| if self._pending_remember is not None: | ||
| await self._pending_remember | ||
| self._pending_remember = None |
| # 5. Distill this turn into facts and store only those in the live session, in the | ||
| # background so it never delays the reply. Awaited at the top of the next turn | ||
| # (step 0) so recall always sees it. Questions/recall add nothing. | ||
| self._pending_remember = asyncio.create_task(self._remember_facts(query)) |
| async def _remember_facts(self, text: str) -> None: | ||
| for fact in await self._extract_facts(text): | ||
| self.turn += 1 | ||
| try: | ||
| await self.session_index.add_docs( | ||
| [DocumentInfo(id=f"fact-{self.turn}", text=fact, metadata={"role": "traveler"})] | ||
| ) | ||
| logger.info(f"Remembered: {fact}") | ||
| except Exception as e: | ||
| logger.warning(f"Failed to store fact: {e}") |
| ], | ||
| ) | ||
| data = json.loads(resp.choices[0].message.content or "{}") | ||
| return [f.strip() for f in data.get("facts", []) if isinstance(f, str) and f.strip()] |
There was a problem hiding this comment.
CONSIDER This assumes the model always returns facts as a list:
return [f.strip() for f in data.get("facts", []) if isinstance(f, str) and f.strip()]If the JSON is valid but shaped as {"facts": "Budget is $2,500"}, Python iterates the string and stores one document per character, polluting the session index. Validate the container first, for example facts = data.get("facts", []); if not isinstance(facts, list): return [], then filter string items.
| # 5. Distill this turn into facts and store only those in the live session, in the | ||
| # background so it never delays the reply. Awaited at the top of the next turn | ||
| # (step 0) so recall always sees it. Questions/recall add nothing. | ||
| self._pending_remember = asyncio.create_task(self._remember_facts(query)) |
There was a problem hiding this comment.
CONSIDER Remembering the user's facts is scheduled only after both Moss lookups succeed:
self._pending_remember = asyncio.create_task(self._remember_facts(query))Because this sits inside the same try as session/catalog retrieval, any transient lookup error drops the current turn from short-term memory entirely. Start the remember task in a separate try/finally or before retrieval so catalog/search failures do not cause session-memory data loss.
|
@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 24 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/travel-concierge/web/components/DualPanel.tsx">
<violation number="1" location="moss-live-labs/examples/travel-concierge/web/components/DualPanel.tsx:41">
P2: A valid-JSON but malformed `moss.retrieval` packet can crash rendering because parsed data is force-cast to `RetrievalPayload` and later used with `toFixed()` without guards. Validating packet shape before `setData` would keep the panel resilient to unexpected data-channel messages.</violation>
</file>
<file name="moss-live-labs/examples/travel-concierge/README.md">
<violation number="1" location="moss-live-labs/examples/travel-concierge/README.md:21">
P2: A fresh setup installs dependencies into uv's `.venv`, but every subsequent `python ...` command uses the shell interpreter and can fail with missing imports. The examples should use `uv run python ...` throughout, or explicitly document virtual-environment activation after `uv sync`.</violation>
</file>
<file name="moss-live-labs/examples/travel-concierge/DEMO_SCRIPT.md">
<violation number="1" location="moss-live-labs/examples/travel-concierge/DEMO_SCRIPT.md:11">
P3: The opening overstates the session behavior: it does not remember everything said, only extracted durable trip preferences. Using the narrower wording would keep the demo consistent with the implementation and the later explanation.</violation>
</file>
<file name="moss-live-labs/examples/travel-concierge/web/package.json">
<violation number="1" location="moss-live-labs/examples/travel-concierge/web/package.json:9">
P2: `npm run lint` is not usable from a clean install because this package provides neither ESLint nor a configuration. Adding `eslint`/`eslint-config-next` plus an `eslint.config.mjs` and invoking `eslint .` would make the advertised check reproducible.</violation>
<violation number="2" location="moss-live-labs/examples/travel-concierge/web/package.json:22">
P2: The Node typings expose APIs newer than the documented Node 18.18 runtime, so type-checking can approve code that fails for supported users. Pinning this to the Node 18 type line keeps compile-time APIs aligned with the minimum runtime.</violation>
</file>
<file name="moss-live-labs/examples/travel-concierge/seed_index.py">
<violation number="1" location="moss-live-labs/examples/travel-concierge/seed_index.py:35">
P2: Catalog seeding can fail or corrupt destination text on systems whose locale encoding is not UTF-8. Specify `encoding="utf-8"` when reading this UTF-8 JSON file.</violation>
</file>
<file name="moss-live-labs/examples/travel-concierge/web/components/AgentSide.tsx">
<violation number="1" location="moss-live-labs/examples/travel-concierge/web/components/AgentSide.tsx:20">
P2: Agent failures are shown to the traveler as “IDLE”, hiding why the voice session is unusable. Handle LiveKit's `failed` state explicitly (and ideally `pre-connect-buffering`) instead of routing every unmapped state through the idle fallback.</violation>
</file>
<file name="moss-live-labs/examples/travel-concierge/web/app/page.tsx">
<violation number="1" location="moss-live-labs/examples/travel-concierge/web/app/page.tsx:37">
P2: The header reports “live” before LiveKit has connected, and connection failures are not surfaced through a retry state because `conn` represents token acquisition rather than room connectivity. Track `onConnected`/`onError` separately and only mark the room live after `onConnected`, clearing back to the start screen on error.</violation>
</file>
<file name="moss-live-labs/examples/travel-concierge/web/app/globals.css">
<violation number="1" location="moss-live-labs/examples/travel-concierge/web/app/globals.css:7">
P2: Placeholder, speaker, and footer text rendered with `--faint` has insufficient contrast on both card and page backgrounds. A darker neutral such as `#706e67` preserves the palette while bringing these small labels above 4.5:1.</violation>
<violation number="2" location="moss-live-labs/examples/travel-concierge/web/app/globals.css:11">
P2: User/session labels and retrieval scores rendered with `--live` are difficult to read at their 11–12px sizes because the color misses normal-text contrast on both white and tinted cards. Darkening this token avoids changing every consumer individually.</violation>
</file>
<file name="moss-live-labs/examples/travel-concierge/web/app/api/token/route.ts">
<violation number="1" location="moss-live-labs/examples/travel-concierge/web/app/api/token/route.ts:11">
P2: Any caller can hit `/api/token` and receive a fully privileged room token, so a public deployment can be abused for unauthorized room usage and resource consumption. Consider adding an app-auth or server-side shared-secret check before minting tokens.</violation>
<violation number="2" location="moss-live-labs/examples/travel-concierge/web/app/api/token/route.ts:36">
P3: The 500 response currently returns raw exception text to the browser, which can expose internal configuration/runtime details. A generic client error body with server-side logging avoids leaking internals while preserving debuggability.</violation>
</file>
<file name="moss-live-labs/examples/travel-concierge/web/components/Transcript.tsx">
<violation number="1" location="moss-live-labs/examples/travel-concierge/web/components/Transcript.tsx:38">
P3: New and updated voice turns are only exposed visually, so screen-reader users are not notified as the conversation changes. Keep a mounted transcript container across the empty/populated states and expose it as a polite `role="log"` live region.</violation>
</file>
<file name="moss-live-labs/examples/travel-concierge/agent.py">
<violation number="1" location="moss-live-labs/examples/travel-concierge/agent.py:10">
P3: The direct `AsyncOpenAI` usage is not represented in this example’s declared dependencies and currently works only through the LiveKit plugin extra. Declare `openai` explicitly so dependency updates cannot silently remove an API this module imports.</violation>
<violation number="2" location="moss-live-labs/examples/travel-concierge/agent.py:102">
P2: Non-text or empty transcription turns pass `None`/blank input into Moss and can fail the whole retrieval path. Normalize and skip blank text before querying or scheduling extraction.</violation>
<violation number="3" location="moss-live-labs/examples/travel-concierge/agent.py:103">
P2: Traveler speech and distilled preferences are persisted in normal INFO logs, exposing potentially sensitive budgets, dates, names, or destinations outside the in-memory session. Log turn metadata only, or gate/redact content behind an explicit debug option.</violation>
<violation number="4" location="moss-live-labs/examples/travel-concierge/agent.py:108">
P1: A slow or stalled fact-extraction request blocks the traveler’s next response indefinitely at this await. Bound the wait and skip/cancel memory extraction on timeout so the voice loop remains responsive.</violation>
<violation number="5" location="moss-live-labs/examples/travel-concierge/agent.py:113">
P2: Travelers with more than three stored facts lose constraints from every recall, including the four-fact utterance in the demo script. Size `top_k` to the number of stored facts, or maintain a consolidated preference document.</violation>
<violation number="6" location="moss-live-labs/examples/travel-concierge/agent.py:118">
P1: Generic follow-ups such as “So where should we go?” retrieve catalog hits without any remembered preferences, so the top three trips need not fit the traveler. Include recalled session facts when constructing the catalog query.</violation>
<violation number="7" location="moss-live-labs/examples/travel-concierge/agent.py:122">
P2: The live-session panel does not show facts from the current utterance; they appear only after a later turn triggers another query. Publish an updated session payload when `_remember_facts` finishes, or include the extracted facts in a follow-up data-channel event.</violation>
<violation number="8" location="moss-live-labs/examples/travel-concierge/agent.py:136">
P2: A lookup failure on one turn also drops that turn’s preferences from future memory because remembering is scheduled only after every retrieval step succeeds. Start `_remember_facts` independently of retrieval success, such as in a `finally` path.</violation>
<violation number="9" location="moss-live-labs/examples/travel-concierge/agent.py:155">
P2: A schema-validity deviation can turn one string-valued `facts` field into dozens of one-character session documents. Validate that `facts` is a list before iterating it, or use Structured Outputs with an array schema.</violation>
<violation number="10" location="moss-live-labs/examples/travel-concierge/agent.py:165">
P2: Corrected preferences remain alongside stale values, so later recall can return contradictory budgets, dates, or party sizes. Use stable category IDs/upserts or delete the superseded fact when a correction is extracted.</violation>
</file>
<file name="moss-live-labs/examples/travel-concierge/web/.gitignore">
<violation number="1" location="moss-live-labs/examples/travel-concierge/web/.gitignore:3">
P2: Credentials placed in other Next.js-supported environment files can still be committed because only `.env.local` is ignored. Cover all `.env*` files while explicitly retaining the example template.</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
| # 0. Make sure the previous turn's facts are stored before we recall, | ||
| # so an immediate follow-up question sees them. | ||
| if self._pending_remember is not None: | ||
| await self._pending_remember |
There was a problem hiding this comment.
P1: A slow or stalled fact-extraction request blocks the traveler’s next response indefinitely at this await. Bound the wait and skip/cancel memory extraction on timeout so the voice loop remains responsive.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At moss-live-labs/examples/travel-concierge/agent.py, line 108:
<comment>A slow or stalled fact-extraction request blocks the traveler’s next response indefinitely at this await. Bound the wait and skip/cancel memory extraction on timeout so the voice loop remains responsive.</comment>
<file context>
@@ -0,0 +1,216 @@
+ # 0. Make sure the previous turn's facts are stored before we recall,
+ # so an immediate follow-up question sees them.
+ if self._pending_remember is not None:
+ await self._pending_remember
+ self._pending_remember = None
+
</file context>
|
|
||
| # 2. Look up matching trips in the pre-loaded catalog (long-term knowledge). | ||
| t = time.perf_counter() | ||
| catalog_results = await self.moss.query(CATALOG_INDEX, query, QueryOptions(top_k=3)) |
There was a problem hiding this comment.
P1: Generic follow-ups such as “So where should we go?” retrieve catalog hits without any remembered preferences, so the top three trips need not fit the traveler. Include recalled session facts when constructing the catalog query.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At moss-live-labs/examples/travel-concierge/agent.py, line 118:
<comment>Generic follow-ups such as “So where should we go?” retrieve catalog hits without any remembered preferences, so the top three trips need not fit the traveler. Include recalled session facts when constructing the catalog query.</comment>
<file context>
@@ -0,0 +1,216 @@
+
+ # 2. Look up matching trips in the pre-loaded catalog (long-term knowledge).
+ t = time.perf_counter()
+ catalog_results = await self.moss.query(CATALOG_INDEX, query, QueryOptions(top_k=3))
+ catalog_ms = (time.perf_counter() - t) * 1000.0
+
</file context>
| "moss.retrieval", | ||
| useCallback((msg: { payload: Uint8Array }) => { | ||
| try { | ||
| setData(JSON.parse(decoder.decode(msg.payload)) as RetrievalPayload); |
There was a problem hiding this comment.
P2: A valid-JSON but malformed moss.retrieval packet can crash rendering because parsed data is force-cast to RetrievalPayload and later used with toFixed() without guards. Validating packet shape before setData would keep the panel resilient to unexpected data-channel messages.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At moss-live-labs/examples/travel-concierge/web/components/DualPanel.tsx, line 41:
<comment>A valid-JSON but malformed `moss.retrieval` packet can crash rendering because parsed data is force-cast to `RetrievalPayload` and later used with `toFixed()` without guards. Validating packet shape before `setData` would keep the panel resilient to unexpected data-channel messages.</comment>
<file context>
@@ -0,0 +1,83 @@
+ "moss.retrieval",
+ useCallback((msg: { payload: Uint8Array }) => {
+ try {
+ setData(JSON.parse(decoder.decode(msg.payload)) as RetrievalPayload);
+ } catch (err) {
+ console.error("failed to parse moss.retrieval payload", err);
</file context>
| @@ -0,0 +1,55 @@ | |||
| # Travel Concierge — pre-loaded catalog + live session | |||
There was a problem hiding this comment.
P2: A fresh setup installs dependencies into uv's .venv, but every subsequent python ... command uses the shell interpreter and can fail with missing imports. The examples should use uv run python ... throughout, or explicitly document virtual-environment activation after uv sync.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At moss-live-labs/examples/travel-concierge/README.md, line 21:
<comment>A fresh setup installs dependencies into uv's `.venv`, but every subsequent `python ...` command uses the shell interpreter and can fail with missing imports. The examples should use `uv run python ...` throughout, or explicitly document virtual-environment activation after `uv sync`.</comment>
<file context>
@@ -0,0 +1,55 @@
+
+## Setup
+```bash
+uv sync
+cp .env.example .env # fill in Moss + provider keys
+python agent.py download-files
</file context>
| "react-dom": "^19.0.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/node": "^22", |
There was a problem hiding this comment.
P2: The Node typings expose APIs newer than the documented Node 18.18 runtime, so type-checking can approve code that fails for supported users. Pinning this to the Node 18 type line keeps compile-time APIs aligned with the minimum runtime.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At moss-live-labs/examples/travel-concierge/web/package.json, line 22:
<comment>The Node typings expose APIs newer than the documented Node 18.18 runtime, so type-checking can approve code that fails for supported users. Pinning this to the Node 18 type line keeps compile-time APIs aligned with the minimum runtime.</comment>
<file context>
@@ -0,0 +1,27 @@
+ "react-dom": "^19.0.0"
+ },
+ "devDependencies": {
+ "@types/node": "^22",
+ "@types/react": "^19",
+ "@types/react-dom": "^19",
</file context>
| @@ -0,0 +1,4 @@ | |||
| node_modules | |||
| .next | |||
| .env.local | |||
There was a problem hiding this comment.
P2: Credentials placed in other Next.js-supported environment files can still be committed because only .env.local is ignored. Cover all .env* files while explicitly retaining the example template.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At moss-live-labs/examples/travel-concierge/web/.gitignore, line 3:
<comment>Credentials placed in other Next.js-supported environment files can still be committed because only `.env.local` is ignored. Cover all `.env*` files while explicitly retaining the example template.</comment>
<file context>
@@ -0,0 +1,4 @@
+node_modules
+.next
+.env.local
+next-env.d.ts
</file context>
| .env.local | |
| .env* | |
| !.env.local.example |
|
|
||
| ## 1 · Frame it (0:00–0:12) | ||
| > "This concierge knows a catalog of trips — that's loaded ahead of time. But it also | ||
| > remembers everything I say on the call. Two Moss indexes, live, side by side. Watch." |
There was a problem hiding this comment.
P3: The opening overstates the session behavior: it does not remember everything said, only extracted durable trip preferences. Using the narrower wording would keep the demo consistent with the implementation and the later explanation.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At moss-live-labs/examples/travel-concierge/DEMO_SCRIPT.md, line 11:
<comment>The opening overstates the session behavior: it does not remember everything said, only extracted durable trip preferences. Using the narrower wording would keep the demo consistent with the implementation and the later explanation.</comment>
<file context>
@@ -0,0 +1,55 @@
+
+## 1 · Frame it (0:00–0:12)
+> "This concierge knows a catalog of trips — that's loaded ahead of time. But it also
+> remembers everything I say on the call. Two Moss indexes, live, side by side. Watch."
+
+**[Click Start planning. The agent greets you.]**
</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: The 500 response currently returns raw exception text to the browser, which can expose internal configuration/runtime details. A generic client error body with server-side logging avoids leaking internals while preserving debuggability.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At moss-live-labs/examples/travel-concierge/web/app/api/token/route.ts, line 36:
<comment>The 500 response currently returns raw exception text to the browser, which can expose internal configuration/runtime details. A generic client error body with server-side logging avoids leaking internals while preserving debuggability.</comment>
<file context>
@@ -0,0 +1,39 @@
+ { headers: { "Cache-Control": "no-store" } },
+ );
+ } catch (error) {
+ const msg = error instanceof Error ? error.message : "Unknown error";
+ return new NextResponse(msg, { status: 500 });
+ }
</file context>
| } | ||
|
|
||
| return ( | ||
| <div className="transcript"> |
There was a problem hiding this comment.
P3: New and updated voice turns are only exposed visually, so screen-reader users are not notified as the conversation changes. Keep a mounted transcript container across the empty/populated states and expose it as a polite role="log" live region.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At moss-live-labs/examples/travel-concierge/web/components/Transcript.tsx, line 38:
<comment>New and updated voice turns are only exposed visually, so screen-reader users are not notified as the conversation changes. Keep a mounted transcript container across the empty/populated states and expose it as a polite `role="log"` live region.</comment>
<file context>
@@ -0,0 +1,47 @@
+ }
+
+ return (
+ <div className="transcript">
+ {ordered.map((turn) => (
+ <div className={`turn ${turn.isUser ? "user" : "agent"}`} key={turn.id}>
</file context>
| from datetime import datetime | ||
|
|
||
| from dotenv import load_dotenv | ||
| from openai import AsyncOpenAI |
There was a problem hiding this comment.
P3: The direct AsyncOpenAI usage is not represented in this example’s declared dependencies and currently works only through the LiveKit plugin extra. Declare openai explicitly so dependency updates cannot silently remove an API this module imports.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At moss-live-labs/examples/travel-concierge/agent.py, line 10:
<comment>The direct `AsyncOpenAI` usage is not represented in this example’s declared dependencies and currently works only through the LiveKit plugin extra. Declare `openai` explicitly so dependency updates cannot silently remove an API this module imports.</comment>
<file context>
@@ -0,0 +1,216 @@
+from datetime import datetime
+
+from dotenv import load_dotenv
+from openai import AsyncOpenAI
+from livekit import rtc
+from livekit.plugins import openai, deepgram, silero, cartesia
</file context>
What
A new voice-agent example under
moss-live-labs/examples/travel-conciergethat shows Moss answering from two indexes in a single call:Each turn the agent recalls the traveler's stated preferences from the session and recommends matching trips from the catalog. Both result sets are published on a
moss.retrievaldata channel, and the web UI renders them side by side, lighting up per turn with per-query latency.Why it's interesting
Contents
agent.py— LiveKit agent querying catalog + session each turnseed_index.py— seeds the catalog cloud indexdata/catalog.json— 14 destinationsweb/— Next.js UI showing both panelsREADME.md,DEMO_SCRIPT.mdTry it
Docs: https://docs.moss.dev/docs/integrate/sessions