diff --git a/examples/python-moss-agent/.env.example b/examples/python-moss-agent/.env.example new file mode 100644 index 00000000..caa3280c --- /dev/null +++ b/examples/python-moss-agent/.env.example @@ -0,0 +1,13 @@ +# LiveKit (only needed for `agent.py dev` - leave blank for `agent.py console`). +LIVEKIT_URL=wss://your-project.livekit.cloud +LIVEKIT_API_KEY= +LIVEKIT_API_SECRET= + +# Moss (sign up free at https://moss.dev) +MOSS_PROJECT_ID=your-moss-project-id +MOSS_PROJECT_KEY=your-moss-project-key + +# Voice pipeline providers +OPENAI_API_KEY=your-openai-api-key +DEEPGRAM_API_KEY=your-deepgram-api-key +CARTESIA_API_KEY=your-cartesia-api-key diff --git a/examples/python-moss-agent/.gitignore b/examples/python-moss-agent/.gitignore new file mode 100644 index 00000000..69cac2e0 --- /dev/null +++ b/examples/python-moss-agent/.gitignore @@ -0,0 +1,4 @@ +.env +.venv/ +__pycache__/ +*.pyc diff --git a/examples/python-moss-agent/README.md b/examples/python-moss-agent/README.md new file mode 100644 index 00000000..0a2c53fa --- /dev/null +++ b/examples/python-moss-agent/README.md @@ -0,0 +1,131 @@ +# Ecommerce Support Voice Agent + +End-to-end LiveKit voice agent built on the +[`moss-agent`](https://pypi.org/project/moss-agent/) +SDK. Showcases the **prewarm-once, attach-per-room** pattern: one +process-wide `MossAgent` with hot indexes loaded at boot, every concurrent +room queries the same warm in-process cache via a one-line +`agent.attach(ctx)`. + +``` +prewarm() runs ONCE per worker process + | + +-- MossAgent(project_id, project_key) + +-- agent.load_indexes(["ecommerce_products", + "ecommerce_faq", + "ecommerce_policies"]) + +handle_visit(ctx) runs PER LiveKit room + | + +-- await ctx.connect() + +-- call = agent.attach(ctx) # one line, scoped to this room + +-- AgentSession(...).start(...) + +-- function_tools route every query through `call.query_multi_index(...)` +``` + +The result: indexes are loaded once at boot, served sub-10ms to hundreds +of concurrent rooms. + +## What this example demonstrates + +| Capability | Where to look | +|---|---| +| Process-wide `MossAgent` + `load_indexes` in `prewarm` | `agent.py` — `prewarm()` | +| Per-room `MossCall` scope via `attach(ctx)` | `agent.py` — `handle_visit()` | +| Multi-index query across three KBs in one call | `agent.py` — `search_store` tool | +| Single-index query (when you know the scope) | `agent.py` — `search_products` tool | +| Three-index ingestion script | `create_indexes.py` | + +## Prerequisites + +- Python 3.10+ +- [uv](https://github.com/astral-sh/uv) (or `pip` + `venv`) +- A [Moss](https://moss.dev) project (`MOSS_PROJECT_ID`, `MOSS_PROJECT_KEY`) +- API keys for: + - [OpenAI](https://platform.openai.com) — LLM + - [Deepgram](https://deepgram.com) — speech-to-text + - [Cartesia](https://play.cartesia.ai) — text-to-speech +- For browser testing only: a [LiveKit](https://livekit.io) project (cloud + or `livekit-server --dev` locally). **Not required for console mode.** + +## Setup + +1. **Install dependencies** + + ```bash + uv sync + ``` + +2. **Configure environment** + + ```bash + cp .env.example .env + ``` + + Fill in your Moss credentials and provider keys. + +3. **Build the Moss indexes** + + ```bash + uv run python create_indexes.py + ``` + + This creates three indexes — `ecommerce_products`, `ecommerce_faq`, + `ecommerce_policies` — from the JSON files in `data/`. Edit those + files to swap in your own catalog, FAQ, or policy text; no code + changes needed. + +4. **Run the agent** + + ```bash + uv run python agent.py console + ``` + + The agent grabs your mic and speakers directly. Press `Ctrl+C` to stop. + +## Try this conversation + +```text +Customer: Hi, what are the Aurora Pro headphones like? +Agent: [search_products] The Aurora Pro is a wireless over-ear with + active noise cancellation, 40-hour battery, USB-C fast charge, + Bluetooth 5.3, and multipoint pairing. It's $279, and comes in + midnight black, sand, and sage. + +Customer: And how long does shipping take? +Agent: [search_store] Standard shipping is free over $50 in the US and + takes 3 to 5 business days. Expedited 2-day is $12, overnight is + $25 if you order before 2pm Eastern. + +Customer: Can I return them if I don't like them? +Agent: [search_store] Yes — 60 days from delivery, free return shipping + via our prepaid label. Original shipping isn't refundable but + everything else hits your card within 5 business days of the + return being scanned. +``` + +Notice the agent uses `search_products` for the first question (clearly +product-scoped) and `search_store` for the next two (broader, the +multi-index search picks the right docs from FAQ vs. policies). + +## Files + +```text +python-moss-agent/ +├── agent.py # Voice agent — prewarm + attach + tools +├── create_indexes.py # Read data/*.json, build three Moss indexes +├── data/ +│ ├── product_catalog.json # 10 products +│ ├── faq.json # 8 FAQ entries +│ └── policies.json # 7 policy docs +├── pyproject.toml # uv-managed dependencies +├── .env.example # Required environment variables +└── README.md # This file +``` + +## Resources + +- [Moss docs](https://docs.moss.dev) +- [Moss llms.txt](https://moss.dev/llms.txt) +- [LiveKit Agents docs](https://docs.livekit.io/agents/) +- [Discord](https://moss.link/discord) diff --git a/examples/python-moss-agent/agent.py b/examples/python-moss-agent/agent.py new file mode 100644 index 00000000..87f62ec0 --- /dev/null +++ b/examples/python-moss-agent/agent.py @@ -0,0 +1,273 @@ +""" +Ecommerce Support Voice Agent +============================= + +Showcase for the ``moss-agent`` SDK's process-wide ``MossAgent`` + per-room +``attach(ctx)`` pattern. + +How it's wired: + + prewarm() (runs ONCE per worker process) + | + +-- MossAgent(project_id, project_key) + +-- agent.load_indexes(["ecommerce_products", + "ecommerce_faq", + "ecommerce_policies"]) + +-- proc.userdata["moss_agent"] = agent + + handle_visit(ctx) (runs PER LiveKit room) + | + +-- await ctx.connect() + +-- call = agent.attach(ctx) <-- one line, scoped to this room + +-- AgentSession(...).start(...) + +-- Tool calls route every query through `call.query_multi_index(...)` + so per-call telemetry (call_id, durationMs) attaches automatically. + +Why this matters: + + The indexes are loaded once at boot. Every concurrent room queries the + same warm in-process cache. There is no per-call cold start, no + duplicated state, and search latency stays in the sub-10ms band even + with hundreds of simultaneous calls. + + ``attach(ctx)`` is idempotent on ``ctx.room.name`` and registers a + shutdown callback so the call scope closes automatically when the room + tears down - no manual lifecycle code in your handler. + +Run:: + + # 1. Build the indexes once + uv run python create_indexes.py + + # 2A. Talk in your terminal (no LiveKit server needed) + uv run python agent.py console + + # 2B. Or register as a worker and dispatch from a browser + uv run python agent.py dev +""" + +from __future__ import annotations + +import logging +import os + +from dotenv import load_dotenv +from livekit.agents import ( + Agent, + AgentSession, + AgentServer, + JobContext, + JobProcess, + RunContext, + cli, + function_tool, +) +from livekit.plugins import cartesia, deepgram, openai, silero + +from moss_agent import MossAgent, MossCall, QueryOptions + +load_dotenv() + +PRODUCT_INDEX = "ecommerce_products" +FAQ_INDEX = "ecommerce_faq" +POLICY_INDEX = "ecommerce_policies" +ALL_INDEXES = [PRODUCT_INDEX, FAQ_INDEX, POLICY_INDEX] + +logging.getLogger("livekit").setLevel(logging.WARNING) +logging.getLogger("livekit.agents").setLevel(logging.WARNING) +logger = logging.getLogger("moss-ecommerce-support") +logger.setLevel(logging.INFO) + +CYAN = "\033[96m" +GREEN = "\033[92m" +YELLOW = "\033[93m" +RESET = "\033[0m" + + +def _require_env(name: str) -> str: + value = os.getenv(name) + if not value: + raise RuntimeError( + f"Missing required environment variable: {name}. " + f"See .env.example for the full list of keys this example needs." + ) + return value + + +# --------------------------------------------------------------------------- +# Process-wide setup: build MossAgent, warm indexes. Runs ONCE per worker. +# --------------------------------------------------------------------------- + +server = AgentServer(num_idle_processes=1) + + +def prewarm(proc: JobProcess) -> None: + """Construct the process-wide ``MossAgent`` and warm its indexes. + + Everything in here runs exactly once per worker process. The agent and + its loaded indexes are then shared by every room this worker handles - + no per-call cold start, no duplicated state. + """ + import asyncio + + agent = MossAgent( + project_id=_require_env("MOSS_PROJECT_ID"), + project_key=_require_env("MOSS_PROJECT_KEY"), + ) + asyncio.run(agent.load_indexes(ALL_INDEXES)) + logger.info( + f"{GREEN}Prewarmed MossAgent (client_id={agent.client_id}) " + f"with {len(ALL_INDEXES)} indexes: {', '.join(ALL_INDEXES)}{RESET}" + ) + proc.userdata["moss_agent"] = agent + + +server.setup_fnc = prewarm + + +# --------------------------------------------------------------------------- +# The voice agent. Owns a `MossCall` scoped to the current room. +# --------------------------------------------------------------------------- + + +class SupportAgent(Agent): + """A single retrieval-grounded support agent. + + Every factual question routes through ``self._call`` - the + :class:`MossCall` that ``attach(ctx)`` returned. That object tags every + query with the LiveKit room's ``call_id`` so per-call telemetry + (durationMs, query count) lands in Moss's event log without any extra + work on our side. + """ + + def __init__(self, call: MossCall): + self._call = call + + super().__init__( + instructions=""" + You are an ecommerce support voice agent for a small online store. + + You can answer questions about: + - Specific products (price, features, availability, colors) + - Shipping, payment, and account questions + - Return, warranty, price-match, and privacy policies + + Rules: + - ALWAYS call `search_store` before answering a factual + question. Never invent prices, SKUs, shipping times, or + policy terms. + - If the question is clearly product-specific (a model name, + a SKU, "do you have X"), call `search_products` to scope + the search to the product catalog. + - Keep answers short and conversational. This is voice - + no bullet points, no markdown, no SKUs read aloud unless + the customer asks for one. + - If the search returns nothing useful, say you'll have a + human follow up - do not guess. + """, + ) + + async def on_enter(self) -> None: + await self.session.say( + "Hi, this is the support line. I can help with our products, " + "shipping, returns, anything store-related. What's going on?" + ) + + @function_tool + async def search_store(self, context: RunContext, question: str) -> str: + """Search the store knowledge base across products, FAQ, and policies. + + Use this for any factual question that isn't obviously scoped to one + of those three categories - the multi-index search lets the embedding + model pick the right docs across all of them. + + Args: + question: The customer's question, rephrased as a search query. + """ + logger.info(f"{CYAN}Moss multi-index query [{self._call.call_id[:8]}]:{RESET} {question}") + try: + results = await self._call.query_multi_index( + ALL_INDEXES, + question, + QueryOptions(top_k=4), + ) + except Exception as e: + logger.error(f"Moss search failed: {e}", exc_info=True) + return "Knowledge base search failed. Tell the customer you'll have a teammate follow up." + + if not results.docs: + return "No relevant information found. Tell the customer you'll have a human follow up." + + logger.info( + f"{GREEN} returned {len(results.docs)} docs in {results.time_taken_ms}ms{RESET}" + ) + for i, doc in enumerate(results.docs, 1): + preview = doc.text[:120] + "..." if len(doc.text) > 120 else doc.text + logger.info(f"{GREEN} [{i}] {preview}{RESET}") + + return "\n".join(f"- {d.text}" for d in results.docs) + + @function_tool + async def search_products(self, context: RunContext, question: str) -> str: + """Search only the product catalog. + + Call this when the customer is clearly asking about a specific + product or product category - "do you sell X", "how much is the Y", + "what colors does Z come in". For broader questions + (shipping, returns, payment) use `search_store` instead. + """ + logger.info(f"{CYAN}Moss product query [{self._call.call_id[:8]}]:{RESET} {question}") + try: + results = await self._call.query( + PRODUCT_INDEX, + question, + QueryOptions(top_k=4, alpha=0.75), + ) + except Exception as e: + logger.error(f"Moss product search failed: {e}", exc_info=True) + return "Product search failed. Ask the customer to rephrase." + + if not results.docs: + return "No matching products found. Offer to search the broader store." + + logger.info( + f"{GREEN} returned {len(results.docs)} docs in {results.time_taken_ms}ms{RESET}" + ) + return "\n".join(f"- {d.text}" for d in results.docs) + + +# --------------------------------------------------------------------------- +# Per-room handler. Runs once per LiveKit room. +# --------------------------------------------------------------------------- + + +@server.rtc_session(agent_name="moss-ecommerce-support") +async def handle_visit(ctx: JobContext) -> None: + """Bind a Moss call scope to this room and run the voice pipeline.""" + await ctx.connect() + + # Grab the process-wide agent and bind a call scope to this room. + # `attach` is idempotent and registers its own shutdown callback. + moss_agent: MossAgent = ctx.proc.userdata["moss_agent"] + call = moss_agent.attach(ctx) + logger.info( + f"{YELLOW}Attached Moss call_id={call.call_id} " + f"to room={call.livekit_room_id}{RESET}" + ) + + session = AgentSession( + stt=deepgram.STT(model="nova-2"), + llm=openai.LLM(model="gpt-4o"), + tts=cartesia.TTS(model="sonic-3-2026-01-12"), + vad=silero.VAD.load(), + turn_handling={"interruption": {"mode": "vad"}}, + ) + + await session.start( + agent=SupportAgent(call), + room=ctx.room, + ) + + +if __name__ == "__main__": + cli.run_app(server) diff --git a/examples/python-moss-agent/create_indexes.py b/examples/python-moss-agent/create_indexes.py new file mode 100644 index 00000000..54c37e3c --- /dev/null +++ b/examples/python-moss-agent/create_indexes.py @@ -0,0 +1,71 @@ +"""Build the three Moss indexes the ecommerce-support voice agent queries. + +The agent's `prewarm()` warms these three indexes into the process cache +once, then every room handler queries them via `attach(ctx)` -> `MossCall`. + +Run once before starting the agent: + + uv run python create_indexes.py +""" + +from __future__ import annotations + +import asyncio +import json +import os +from pathlib import Path + +from dotenv import load_dotenv +from moss_agent import DocumentInfo, MossAgent + +load_dotenv() + +DATA_DIR = Path(__file__).parent / "data" + +INDEXES: dict[str, str] = { + "ecommerce_products": "product_catalog.json", + "ecommerce_faq": "faq.json", + "ecommerce_policies": "policies.json", +} + + +def _require_env(name: str) -> str: + value = os.getenv(name) + if not value: + raise RuntimeError(f"Missing required environment variable: {name}") + return value + + +def load_documents(filename: str) -> list[DocumentInfo]: + path = DATA_DIR / filename + with path.open() as f: + raw = json.load(f) + docs: list[DocumentInfo] = [] + for doc in raw: + # Moss requires string-valued metadata. Coerce so the JSON can + # still write ints/bools naturally (e.g., "price_usd": 279). + metadata = {k: str(v) for k, v in doc.get("metadata", {}).items()} + docs.append( + DocumentInfo(id=doc["id"], text=doc["text"], metadata=metadata) + ) + return docs + + +async def main() -> None: + agent = MossAgent( + project_id=_require_env("MOSS_PROJECT_ID"), + project_key=_require_env("MOSS_PROJECT_KEY"), + ) + + for index_name, filename in INDEXES.items(): + docs = load_documents(filename) + print(f"Creating Moss index '{index_name}' with {len(docs)} documents from {filename}...") + await agent.create_index(index_name, docs) + print(f" done: '{index_name}'") + + print("\nAll three indexes are ready. Start the agent with:") + print(" uv run python agent.py console") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/python-moss-agent/data/faq.json b/examples/python-moss-agent/data/faq.json new file mode 100644 index 00000000..0e0512bd --- /dev/null +++ b/examples/python-moss-agent/data/faq.json @@ -0,0 +1,42 @@ +[ + { + "id": "faq-shipping-time", + "text": "Standard shipping is free for orders over $50 in the continental US and arrives in 3-5 business days. Expedited 2-day shipping is $12. Overnight is $25 and must be ordered before 2pm Eastern.", + "metadata": {"topic": "shipping"} + }, + { + "id": "faq-international-shipping", + "text": "We ship to Canada, the UK, Australia, and the EU. International orders take 7-14 business days. Duties and taxes are calculated at checkout - the price you see is the price you pay, no surprise customs bills.", + "metadata": {"topic": "shipping"} + }, + { + "id": "faq-order-tracking", + "text": "You will receive a tracking email when your order ships - usually within 24 hours of placing it. Tracking links go live within a few hours of the carrier picking the package up. If 48 hours have passed and the link still says 'label created', the carrier has the package but has not scanned it yet.", + "metadata": {"topic": "shipping"} + }, + { + "id": "faq-payment-methods", + "text": "We accept Visa, Mastercard, American Express, Discover, Apple Pay, Google Pay, Shop Pay, and PayPal. We do not accept checks, money orders, or cryptocurrency.", + "metadata": {"topic": "payment"} + }, + { + "id": "faq-gift-cards", + "text": "Digital gift cards are available in $25, $50, $100, and $250 denominations. They arrive by email within 5 minutes of purchase, never expire, and can be combined with other gift cards or promo codes at checkout.", + "metadata": {"topic": "payment"} + }, + { + "id": "faq-account-required", + "text": "You do not need an account to place an order - guest checkout is supported. Creating an account lets you track previous orders, save shipping addresses, and earn loyalty points (1 point per dollar, $1 off per 100 points).", + "metadata": {"topic": "account"} + }, + { + "id": "faq-promo-code-stacking", + "text": "Only one promo code can be applied per order. Loyalty point redemptions and gift card balances can be combined with a promo code. Sale prices stack with promo codes unless the promo specifically says 'full-price items only'.", + "metadata": {"topic": "promotions"} + }, + { + "id": "faq-product-availability", + "text": "Items in stock ship same-day if ordered before 12pm Eastern. Backordered items show an estimated restock date on the product page; we charge your card only when the item ships. Sold-out items have a 'notify me' button that emails you when stock returns.", + "metadata": {"topic": "inventory"} + } +] diff --git a/examples/python-moss-agent/data/policies.json b/examples/python-moss-agent/data/policies.json new file mode 100644 index 00000000..131b74d1 --- /dev/null +++ b/examples/python-moss-agent/data/policies.json @@ -0,0 +1,37 @@ +[ + { + "id": "pol-returns-window", + "text": "Returns are accepted within 60 days of delivery for any reason. Items must be in original condition with all packaging and accessories. Original shipping is non-refundable; return shipping is free via our prepaid label.", + "metadata": {"topic": "returns"} + }, + { + "id": "pol-returns-process", + "text": "Start a return at usestore.example/returns with your order number and email. Print the prepaid label, drop the package at any UPS location, and the refund hits your original payment method within 5 business days of the package being scanned.", + "metadata": {"topic": "returns"} + }, + { + "id": "pol-warranty-audio", + "text": "Aurora audio products carry a 2-year limited warranty against manufacturing defects. Battery degradation below 70% of original capacity within the warranty window is covered. Damage from drops, liquid exposure, or unauthorized repair is not covered.", + "metadata": {"topic": "warranty", "category": "audio"} + }, + { + "id": "pol-warranty-general", + "text": "All other products carry a 1-year limited warranty against manufacturing defects. To claim, email support@usestore.example with your order number, a photo of the defect, and a one-sentence description. Most claims are resolved with a replacement shipped within 3 business days.", + "metadata": {"topic": "warranty"} + }, + { + "id": "pol-price-match", + "text": "We price-match identical items from major retailers (Amazon, Best Buy, Target, Walmart) within 14 days of purchase. Email support with the competitor URL and we will refund the difference to your original payment method.", + "metadata": {"topic": "pricing"} + }, + { + "id": "pol-cancel-order", + "text": "Orders can be cancelled for a full refund up until the moment they ship - usually within 2 hours of being placed. Once a tracking number is generated the order cannot be cancelled; you can refuse delivery or return it through the normal return process.", + "metadata": {"topic": "orders"} + }, + { + "id": "pol-privacy", + "text": "We do not sell or share customer email addresses, order history, or payment information with third parties. Aggregated, non-identifying analytics may be shared with marketing partners. Account data can be exported or deleted on request at usestore.example/privacy.", + "metadata": {"topic": "privacy"} + } +] diff --git a/examples/python-moss-agent/data/product_catalog.json b/examples/python-moss-agent/data/product_catalog.json new file mode 100644 index 00000000..6cec0b03 --- /dev/null +++ b/examples/python-moss-agent/data/product_catalog.json @@ -0,0 +1,52 @@ +[ + { + "id": "p-aurora-headphones", + "text": "Aurora Pro wireless over-ear headphones. Active noise cancellation, 40-hour battery, USB-C fast charge (10 minutes for 5 hours playback), Bluetooth 5.3, multipoint pairing for two devices. $279. Colors: midnight black, sand, sage.", + "metadata": {"category": "audio", "sku": "AUR-OE-001", "price_usd": 279} + }, + { + "id": "p-aurora-buds", + "text": "Aurora Buds true wireless earbuds. ANC plus transparency mode, 8 hours per bud (28 hours with case), IPX5 sweat resistant, wireless charging case. Half the noise cancellation of the over-ear Pro. $149. Colors: black, white.", + "metadata": {"category": "audio", "sku": "AUR-TW-002", "price_usd": 149} + }, + { + "id": "p-luma-lamp", + "text": "Luma desk lamp. Full-spectrum LED, 5 color temperatures from 2700K warm to 6500K daylight, USB-C output for charging your phone from the base. Touch-dim, no buttons. $89.", + "metadata": {"category": "home", "sku": "LUM-DSK-010", "price_usd": 89} + }, + { + "id": "p-luma-floor", + "text": "Luma floor lamp. Same full-spectrum panel as the desk lamp scaled to a 60-inch column, foot-tap controls, motion-activated dim-to-night mode. $229.", + "metadata": {"category": "home", "sku": "LUM-FLR-011", "price_usd": 229} + }, + { + "id": "p-nimbus-backpack", + "text": "Nimbus 22L commuter backpack. Padded 16-inch laptop sleeve, weatherproof recycled ripstop shell, magnetic chest strap, hidden RFID-blocking passport pocket. Carry-on legal on every major US airline. $139.", + "metadata": {"category": "bags", "sku": "NIM-BP-022", "price_usd": 139} + }, + { + "id": "p-nimbus-sling", + "text": "Nimbus crossbody sling. 4L, fits an iPad mini, water bottle pocket, anti-theft locking zipper. $69.", + "metadata": {"category": "bags", "sku": "NIM-SL-004", "price_usd": 69} + }, + { + "id": "p-tide-bottle", + "text": "Tide vacuum-insulated bottle. 24oz, keeps cold drinks cold for 36 hours and hot drinks hot for 12. Dishwasher-safe lid. $39. Colors: storm, dune, moss.", + "metadata": {"category": "drinkware", "sku": "TID-BTL-024", "price_usd": 39} + }, + { + "id": "p-tide-mug", + "text": "Tide travel mug. 16oz, leak-proof flip lid, fits standard car cup holders, double-wall vacuum insulation. $29.", + "metadata": {"category": "drinkware", "sku": "TID-MUG-016", "price_usd": 29} + }, + { + "id": "p-helix-keyboard", + "text": "Helix 75% mechanical keyboard. Hot-swap sockets, gasket-mounted PCB, lubed tactile switches, RGB underglow, Bluetooth + 2.4GHz + USB-C wired. Mac and Windows layouts both ship with every order. $199.", + "metadata": {"category": "desk", "sku": "HLX-KB-075", "price_usd": 199} + }, + { + "id": "p-helix-mouse", + "text": "Helix wireless mouse. 26K DPI optical sensor, 6 programmable buttons, 70-hour battery, 1ms latency on the 2.4GHz dongle. $89.", + "metadata": {"category": "desk", "sku": "HLX-MS-001", "price_usd": 89} + } +] diff --git a/examples/python-moss-agent/pyproject.toml b/examples/python-moss-agent/pyproject.toml new file mode 100644 index 00000000..fff1f06f --- /dev/null +++ b/examples/python-moss-agent/pyproject.toml @@ -0,0 +1,11 @@ +[project] +name = "ecommerce-support-voice-agent" +version = "0.1.0" +description = "LiveKit voice agent built on the moss-agent SDK. Prewarms a process-wide MossAgent once and serves every room from the same warm in-process cache." +readme = "README.md" +requires-python = ">=3.10" +dependencies = [ + "python-dotenv>=1.0.0", + "moss-agent[livekit]>=1.0.0", + "livekit-agents[openai,deepgram,silero,turn-detector,cartesia]>=1.0", +]