尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 README ↗A browser-based voice customer support agent for Aura Skincare, a fictional premium organic Indian skincare brand. Click Start Call, speak into your mic, and Aria answers out loud. She can look up live orders, apply return and cancellation policy, decline out-of-scope requests, and produce a transcript plus a structured JSON outcome when the call ends.
Approach in brief: A modular pipeline that runs in the browser: Web Speech API speech-to-text (en-IN), a Groq-hosted
gpt-oss-120bagent (low reasoning effort) with function calling for order lookups and policy checks, and sentence-by-sentence browser text-to-speech with an Indian English voice. Policy decisions (return windows, cancellation rules) live in deterministic code that the LLM calls as tools, so the model decides when to check and the code decides what is true. Replies stream token by token and Aria starts speaking after the first sentence, which keeps the gap between turns short.
| Requirement | How it's handled |
|---|---|
| Natural voice conversation | Continuous en-IN speech recognition with custom end-of-turn detection; streamed replies spoken sentence by sentence |
| Indian voice / persona | Auto-selects an en-IN voice (Neerja/Heera/Veena…) and falls back gracefully; persona "Aria" set in the system prompt |
| Context | Full conversation history (including tool calls and results) is sent each turn |
| Brand knowledge and guardrails | Single knowledge source (lib/knowledge.js) feeds both the prompt and the policy code |
| Order lookup tool | get_order_details(order_id), plus check_return_eligibility, cancel_order (requires confirmation) and escalate_to_human |
| Invalid / missing IDs | Tool returns a structured "not found" result; the agent asks the customer to repeat or verify, and offers a ticket after two failures |
| Unclear audio | Low-confidence transcripts are tagged [unclear audio] so the agent asks the customer to repeat instead of guessing |
| Post-call summary | Chronological transcript (with tool calls) and a JSON outcome: LLM judgement fields merged with computed facts |
| Testing UI | Start/End, live Listening/Thinking/Speaking indicator, mic level meter, test-orders panel, one-click test prompts, typed fallback |
| Hinglish | Hinglish/Hindi is detected in code each turn and Aria replies in Roman-script Hinglish |
| Resilience | Automatic fallback to another Groq model on rate limits or outages; polite spoken fallback if everything fails |
| Extras | Barge-in (talk over Aria to interrupt), a short spoken filler ("Let me check that for you") while a tool runs, which cuts first-audio time on order lookups from ~2 s to ~0.9 s, per-turn latency readout, "are you still there?" prompt, text mode if mic is unavailable |
Browser (public/) Server (api/ + lib/) Groq
───────────────── ──────────────────── ────
Mic ─► Web Speech API (en-IN, continuous)
│ interim text ─► live captions / barge-in check
│ end-of-turn (750 ms after last final result)
▼
POST /api/chat {messages} ───────────────────► sanitize history
agent loop ─────────── stream ───────► gpt-oss-120b
(on 429: gpt-oss-20b → qwen3)
│ ◄── tool_call(get_order_details) ── (tool calling)
│ run tool in code (orders.js / policy)
│ ── tool result ───────────────────►
◄── NDJSON events: tool_start, tool, text, done ── │ ◄── text tokens (streamed) ────────
│
sentence splitter ─► speechSynthesis queue (Indian voice) ─► Speakers
│
End Call ─► POST /api/summary {transcript} ─────► computed facts + LLM JSON mode ───────►
◄── structured outcome JSON ─────────────
SpeechRecognition runs continuously in en-IN with interim results, so the customer sees live captions. Chrome restarts recognition every ~60 s or after silence; the Listener class restarts it transparently./api/chat. The server runs the tool loop against Groq with stream: true and forwards events as newline-delimited JSON.speechSynthesis, so Aria starts speaking after the first sentence instead of waiting for the whole reply. The UI shows the time from end of speech to first audio for each turn (the ⚡ chip).The model gets four JSON-schema tools with clear descriptions, plus prompt rules such as "never guess order details; to talk about a specific order call get_order_details first" and "for any return/refund request call check_return_eligibility and follow its result". With tool_choice: "auto" the model decides per turn: a question about ORD-101 produces a tool call, while a COD question or "book me a flight" doesn't. The server executes the call, appends the result as a tool message, and loops (up to 4 rounds; the last round withholds tools so the model has to answer).
Tools are designed for speech input: normalizeOrderId accepts "ORD-101", "ord 101", "order one zero one" or just "101". Every tool returns structured data and never throws; errors come back as instructions the model can relay ("No order found with ID ORD-999. Ask the customer to repeat or verify").
Guardrails are layered so no single layer has to be perfect:
check_return_eligibility and cancel_order compute eligibility from the order data and the policy constants (7-day window, unopened only, 48-hour damage reports, cancel only while Processing). The LLM can't talk its way around a false.cancel_order refuses unless customer_confirmed: true, and the prompt requires an explicit "yes" first.user/assistant/tool messages are accepted (an injected system message is dropped), history is capped, and there is per-IP rate limiting./api/summary merges two kinds of fields:
customer_intent, secondary_intents, order_id, resolution_status (RESOLVED | PARTIALLY_RESOLVED | UNRESOLVED | ESCALATED | POLICY_DECLINED), sentiment, policy applied, actions taken, follow-up flag, and a short summary. Enum values are validated.If the LLM call fails, a rule-based fallback summary is returned (summary_source: "fallback"), so the screen never breaks.
Example:
{
"customer_intent": "RETURN_REQUEST",
"secondary_intents": [],
"order_id": "ORD-102",
"resolution_status": "POLICY_DECLINED",
"customer_sentiment": "NEUTRAL",
"policy_applied": "7-day return window, unopened products only",
"actions_taken": ["Looked up ORD-102", "Checked return eligibility"],
"follow_up_required": false,
"call_summary": "Customer asked to return the opened sunscreen from ORD-102, delivered 14 days ago. Aria explained it falls outside the 7-day return policy for unopened items.",
"call_duration_seconds": 48,
"customer_turns": 2,
"agent_turns": 3,
"tools_used": ["check_return_eligibility"],
"orders_referenced": ["ORD-102"],
"escalation_ticket": null,
"summary_source": "llm"
}
api/ Serverless handlers (Vercel); also used by node-server.js
chat.js POST /api/chat → streams agent events (NDJSON)
summary.js POST /api/summary → structured call outcome
orders.js GET /api/orders → mock orders for the helper panel
lib/
agent.js System prompt, history sanitising, tool-calling loop
tools.js Tool schemas + deterministic policy implementations
orders.js Mock order DB + spoken order-ID normaliser
knowledge.js Brand facts and policy constants (single source of truth)
groq.js Minimal fetch-based Groq client with SSE parsing
summary.js Post-call summary (computed facts + LLM JSON)
http.js Body parsing, JSON responses, rate limiting
public/
index.html, styles.css
app.js Call state machine, UI, streaming, barge-in
speech.js Listener (STT + endpointing), Speaker (TTS queue), helpers
node-server.js Zero-dependency local/Render server (excluded from Vercel via .vercelignore)
test/ Unit tests (node:test)
scripts/eval.js Scenario evals against the real model
No npm dependencies at all: just Node 18+ and fetch.
git clone <this repo>
cd <repo folder>
cp .env.example .env # then paste your free Groq key from https://console.groq.com/keys
npm start # http://localhost:3000
Open http://localhost:3000 in Chrome or Edge, click Start Call, and allow microphone access. (Browsers only allow the mic on localhost or HTTPS.)
npm test # unit tests: ID normalisation, policy decisions, tools, echo filter, sentence splitting
npm run eval # 8 live scenarios against Groq: tracking, policy refusal, out-of-scope, invalid/missing ID, cancel rules, Hinglish, prompt injection
npm run eval -- hinglish # run only scenarios whose name matches
Latest eval run: 8/8 passing, with full replies (including tool calls) in roughly 0.6–1.7 s.
vercel.json serves public/ and the functions in api/).GROQ_API_KEY, then deploy.New Web Service → Build command: (none), Start command: npm start → add GROQ_API_KEY.
I chose a modular STT → LLM → TTS pipeline over a speech-to-speech realtime model because the assignment is mostly about control: following policy, calling tools reliably, and not promising things. A modular pipeline gives me a text checkpoint at every step. I can log exactly what the model heard, inspect every tool call, unit-test the policy logic, and swap any component independently.
openai/gpt-oss-120b for the reasoning: reliable function calling and very fast token generation on Groq, which matters more for voice than for chat. It's a reasoning model, so I set reasoning_effort: low: enough thinking to pick the right tool, not enough to create dead air. It also has a free tier, so the public demo costs nothing to run.en-IN recognition, and Indian English voices on Edge and Windows. For a demo that evaluators must open and use instantly, this removed a whole class of setup and cost problems.Turn-taking: knowing when the customer has finished, and when they're interrupting. Three problems interact:
On the LLM side, two practical problems showed up once I ran the evals against the real API:
gpt-oss-20b and then qwen3, and remembers the cooldown so it skips a throttled model straight away.scripts/eval.js.Malformed tool calls (tool_use_failed) are retried once without streaming; if everything fails, Aria says a polite "could you say that again" rather than crashing.
Audio quality and latency, by moving STT and TTS server-side with streaming providers (e.g. Deepgram or Sarvam for Indian-accent STT and Hinglish, plus a streaming Indian-voice TTS). Browser speech is the weakest link: it varies by browser, struggles with accents and alphanumeric IDs, and has no real acoustic echo cancellation. I'd stream mic audio over a WebSocket with proper VAD-based endpointing and AEC from getUserMedia. That gives consistent voice quality on every browser and makes barge-in reliable on laptop speakers. After that: customer verification (registered phone or OTP) before revealing order details, and a larger eval set run on every prompt change.
1,000 calls a day is modest load (roughly 2–4 concurrent calls at peak), so the work is mostly about reliability, quality and operations, not raw scale:
scripts/eval.js, but hundreds of cases) in CI for every prompt or model change, use an LLM-as-judge on a sample of real calls for policy compliance, and have humans review flagged calls.