Hey, I want an agent that is not necessarily conve...
# ask-ai
s
Hey, I want an agent that is not necessarily conversational but more so only reactive to the user. For example it doesn't try to keep the conversation going if the user does not respond, but it only responds when taken user input. does livekit's agent sdk do this? I am wondering because I am coming from elevenlabs agents to something more robust and elevenlabs agents does not really allow for this.
t
Thanks for reaching out, Sujal. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I’m responding to your question about whether LiveKit’s Agent SDK can run a strictly reactive agent that only replies when given user input (no unsolicited follow-ups). Yes. LiveKit agents by default wait for user input before responding and do not proactively continue conversations unless you program them to. To ensure purely reactive behavior: • Don’t call lifecycle methods that generate speech automatically (e.g., avoid
session.generate_reply()
or
session.say()
in
on_enter
, timers, or other hooks). • Keep your agent
instructions
focused on replying only to the current user input and not prompting further conversation. • Trigger responses only from explicit user events (incoming messages, audio input, or your app’s explicit call). This control is available in both the Python and Node.js SDKs, so you can implement a reactive-only agent as you move from ElevenLabs. If you want, tell me which SDK you plan to use and I’ll give a short code example showing a reactive-only setup. Sources: Agent speech and audio | LiveKit docs
s
Awesome. does this mean i make turn detection manual? also when detecting speech, i want to use Speech diarization alongside speaker recognition to see if the people who are speaking are specific people I want to know are talking or not. additionally if they are unknown, the system will keep track of them and assign them descriptions of what their role potentially is in the conversation. does livekit work for this/
t
Thanks — I’m responding to your question about (1) whether turn detection must be manual for a reactive-only agent, and (2) whether LiveKit supports diarization, speaker recognition, and role assignment. Short answers • Manual turn detection is optional. LiveKit supports automatic turn detection (VAD, STT endpointing, turn-detector models, realtime model detection) and also lets you disable automatic detection and manage turns manually. • Speech diarization is supported via STT plugins (e.g., Speechmatics, Deepgram) and will provide speaker-tagged transcripts. • Speaker recognition (identifying a known person by voice) is not provided natively. Diarization differentiates voices but does not verify identity. • Dynamic role inference for unknown speakers is not built-in, but you can implement it yourself by feeding diarized transcripts into an LLM or rules engine and storing inferred roles. How you can implement your exact workflow 1. Use automatic turn detection unless you want full control. If you want strictly reactive replies triggered only by explicit user events, disable automatic turn handling and trigger responses from your app events (manual turn control). 2. Enable an STT plugin that supports diarization (Speechmatics or Deepgram) to get speaker-labeled segments. 3. For identity verification, stream or batch the diarized audio segments to a third‑party speaker-recognition service (e.g., Azure Speaker Recognition, Amazon Voice ID) and attach any identity metadata back to the speaker IDs from diarization. 4. For unknown speakers, run a role-inference step (an LLM prompt or rule-based classifier) against their diarized transcripts and store the role/description as metadata tied to that speaker ID; update as conversation continues. If you want, tell me: • Which SDK (Node or Python) you’ll use, • Which STT provider you prefer (Speechmatics, Deepgram, etc.), and I’ll provide a short example showing how to (a) disable automatic turns and trigger replies only on events, and (b) extract diarized segments for external speaker-recognition and role-inference. Sources: Turns overview | LiveKit docs | LiveKit turn detector plugin | LiveKit docs | Improving voice AI's turn detection with transformers | xAI Grok Voice Agent API plugin | LiveKit docs | Speechmatics STT plugin guide | LiveKit docs
s
From what I know I prefer Node and Speechmatics
t
Great — I’ll show a concise Node.js plan and example for your exact setup: manual/reactive turns + Speechmatics diarization + how to plug in external speaker recognition and role inference. What this will do • Disable automatic turns so the agent only responds when you explicitly commit a user turn. • Enable Speechmatics diarization to get speaker-tagged transcript segments. • Show where to send diarized audio segments to an external speaker-recognition service and where to run a role-inference LLM to assign/update roles. Before I give code: do you want push-to-talk (user toggles mic) or server-detected manual control (your server enables/disables audio programmatically)? Sources: Speechmatics STT plugin guide | LiveKit docs | Turns overview | LiveKit docs | Text and transcriptions | LiveKit docs
s
I'm not quite sure if I want manual turns themselves but what I'm looking for is the agent doesn't say anything unless spoken to. That's mainly what I'm after. Yeah you can give me an example
t
Thanks — I’ll show a concise Node.js example that makes the agent strictly reactive (it only speaks when spoken to), enables Speechmatics diarization, and marks where to plug in external speaker-recognition and role-inference logic. Prerequisites • Node.js agent SDK and Speechmatics plugin installed. • Speechmatics credentials. • An LLM or service for role inference and an external speaker-recognition service if you need identity matching. Example (Node.js)
Copy code
// Reactive agent + Speechmatics diarization (Node.js)
// Sketch — adapt imports/paths to your project

import { voice } from '@livekit/agents';
import * as speechmatics from '@livekit/agents-plugin-speechmatics';

// Create STT with diarization enabled
const stt = new speechmatics.STT({
  apiKey: process.env.SPEECHMATICS_API_KEY,
  enableDiarization: true,                    // enable diarization
  speakerActiveFormat: '<{speaker_id}>{text}<!--{speaker_id}-->',
});

// Create session in manual/reactive mode: the agent will not auto-start replies.
// turnDetection 'auto' still works but we set manual control to ensure no unsolicited speech.
const session = new voice.AgentSession({
  stt,
  turnDetection: 'manual', // prevents automatic turn-generation by the agent
  // ... include your llm, tts, vad, etc.
});

// Prevent agent from speaking unless explicitly triggered
// (No on_enter or timers that call session.say()/generate_reply()).

// Hook: when STT produces a transcription with diarization
session.on('stt.transcript', async (event) => {
  // event.text will include speaker tags per speakerActiveFormat, e.g. "<s1>Hello</s1>"
  const diarizedText = event.text;
  const speakerId = event.speakerId ?? extractSpeakerId(diarizedText); // depends on plugin output
  const audioSegment = event.audioSegment; // audio tied to this transcript segment (if provided)

  // 1) Optional: call external speaker-recognition to map speakerId -> known identity
  // If unknown, store a persistent internal id mapping and keep track of metadata
  const identity = await callSpeakerRecognitionService(audioSegment, speakerId);
  if (identity) {
    // attach identity metadata to session/memory
    session.memory?.setSpeakerMeta(speakerId, { identity });
  } else {
    // assign or update a temporary label/description
    const inferredRole = await inferRoleForSpeaker(diarizedText, speakerId);
    session.memory?.setSpeakerMeta(speakerId, { role: inferredRole });
  }

  // 2) Decide whether this segment should trigger the agent to reply.
  // Example policy: agent only replies when directly addressed or when a trigger phrase appears.
  const addressed = isDirectlyAddressed(diarizedText); // implement this check
  if (addressed) {
    // Commit user turn and generate reply once — agent will speak only now
    session.commitUserTurn({ speakerId, text: diarizedText });
    // session.generate_reply() or session.say() will be invoked by commitUserTurn flow
  } else {
    // Do nothing — agent remains silent
    // Optionally store the user turn for context without invoking a reply
    session.addContext({ speakerId, text: diarizedText });
  }
});

// Utility placeholders — implement per your stack
function extractSpeakerId(text) {
  const m = text.match(/<(\w+)>/);
  return m ? m[1] : 'unknown';
}

async function callSpeakerRecognitionService(audioSegment, speakerId) {
  // send audioSegment to external speaker-ID service
  // return identity object or null
  return null;
}

async function inferRoleForSpeaker(text, speakerId) {
  // call your LLM with diarized conversation snippets to infer role
  // return string like "interviewer", "customer", etc.
  return 'participant';
}

function isDirectlyAddressed(text) {
  // Basic example: check for agent name or direct address phrases
  const triggers = ['hey assistant', 'assistant,', 'you okay', 'can you'];
  const lower = text.toLowerCase();
  return triggers.some(t => lower.includes(t));
}
Key notes • turnDetection: 'manual' ensures the agent won’t auto-initiate speech; you control commits that trigger replies. • Use diarized STT output to separate speakers (S1, S2) and collect audio segments per speaker for external speaker-recognition. • Only call
session.commitUserTurn()
/
session.generate_reply()
when you want the agent to respond. Otherwise store context silently. • Role inference and identity matching must be implemented outside LiveKit (LLM prompts, speaker-recognition APIs). Attach results to speaker metadata for later decisions. If you’d like, I can: • Provide a tighter push-to-talk example (user toggles mic). • Draft an LLM prompt template to infer roles from diarized text. • Show a sample flow that batches diarized audio and calls Azure/Amazon speaker-recognition. Which would you prefer next?
s
I would also like the agent to understand emotion though which I knew realtime agents can do that much better. is there a way to make realtime agents like one with gpt realtime mini work with this setup where I have speaker recognition?
t
Good — you want a Node.js setup where the agent only speaks when addressed, plus diarization (Speechmatics), speaker recognition, and realtime emotion understanding, possibly using a realtime model (e.g., GPT Realtime Mini). Short answer and recommended architecture, then next step offer. Short answers • Yes — you can make the agent only speak when addressed by using manual/reactive turn control and only committing turns when a direct-address trigger is detected. • Use Speechmatics for diarization to get speaker-labeled segments. • Speaker recognition (mapping voices to known identities) is not built in — run an external speaker-ID service on the diarized audio segments. • Realtime models (GPT Realtime Mini) can be used for low-latency responses and can reflect emotion, but they do not provide raw STT output or diarization. To have diarization + speaker recognition + realtime model, run the realtime model and Speechmatics (plus speaker-ID/emotion processors) in parallel and merge metadata into the agent context. Recommended architecture (concise) • Single incoming audio stream -> split to: ◦ Realtime agent (GPT Realtime Mini) for low-latency reply generation and prosody-aware responses. ◦ Speechmatics STT stream for diarization (speaker IDs + transcripts). ◦ Emotion classifier (optional) for per-segment emotion labels. ◦ Speaker-recognition service for identity matching on diarized audio segments. • A small orchestration layer collects diarized speaker IDs, identity matches, and emotion tags, and injects that metadata into the realtime agent’s context or instructions before/while the agent generates a reply. • Decision policy: only call the agent to reply when a direct-address trigger or other business rule fires (e.g., wake word, mention, explicit push-to-talk). Practical notes & limitations • Realtime models don’t expose raw transcripts; you must run STT (Speechmatics) in parallel for diarization and to extract audio segments for speaker-ID. • Speechmatics Node.js plugin availability is uncertain; you can use Speechmatics’ HTTP/streaming API if a Node plugin isn’t available. • Emotion via the realtime model can influence responses but won’t give explicit emotion labels; use a dedicated emotion classifier if you need discrete emotion tags. • Speaker recognition requires a third-party service or custom model; diarization gives stable anonymous IDs (S1, S2) you can map to identities when recognized. Next step I can provide a focused Node.js example that: • Splits the audio to Realtime model + Speechmatics, • Shows how to detect direct-address triggers and only commit turns then, • Sends diarized audio segments to an external speaker-ID and emotion service and merges metadata into the agent context. Do you want the example now? If yes, confirm: (A) Realtime model to use (OpenAI Realtime / GPT Realtime Mini), and (B) whether you prefer push-to-talk or automatic wake-word/direct-address detection. Sources: Speechmatics STT plugin guide | LiveKit docs | Live Conversations with AI using ChatGPT and WebRTC
s
Yeah mainly what I think the real-time agent would be good for would be recognizing emotion. But if there's a better way to implement emotional recognition, then there's no point for the real-time. So maybe we can have emotional recognition additionally on top without using real-time. How would you potentially implement this?
t
Thanks — I’m responding to your question about implementing emotion recognition without using realtime agents, while keeping the agent strictly reactive with diarization and speaker recognition. Short recommendation • Do emotion detection offline/near‑real‑time in parallel to STT (no need for realtime LLMs). Run Speechmatics for diarization, send diarized audio segments to a dedicated emotion classifier, and pass emotion metadata into your agent’s context when you decide to respond. This gives accurate, tunable emotion labels and keeps low complexity/cost vs. realtime LLMs. Architecture (concise) • Live audio -> split to: ◦ Speechmatics STT (diarization) -> speaker-tagged transcripts + timestamps ◦ Emotion classifier -> per-segment emotion labels (angry, sad, neutral, etc.) ◦ Speaker-recognition service -> map diarized segments to known identities (optional) • Orchestration layer collects {speakerId, transcript, timestamp, emotion, identity} and stores in session memory. • Agent decision logic: only call commit/generate reply when direct-address trigger detected; include emotion+identity metadata in the prompt/context. Why this is preferable to realtime LLM for emotion • Dedicated emotion models give explicit emotion labels you can act on; realtime LLMs can reflect tone but don’t expose discrete labels. • Easier to tune thresholds and audit decisions. • Lower latency requirements manageable with streaming/near-real-time STT + emotion model. Component suggestions • Diarization: Speechmatics (STT plugin or streaming API). • Emotion classifier options: ◦ Prebuilt APIs: AssemblyAI emotion endpoint, Google Cloud Speech-to-Text + sentiment features (where available), or other emotion APIs. ◦ Open models: Wav2Vec2 or WavLM fine-tuned for emotion classification, or small CNN/RNN classifiers (PyTorch/TensorFlow). Use ONNX or TensorFlow.js for server-side inference if needed. • Speaker recognition: Azure Speaker Recognition, Amazon Voice ID, or a custom embedding+matching pipeline (e.g., speaker embeddings via pyannote or Resemblyzer). • Storage/context: attach metadata to LiveKit session memory or your own session store. Minimal Node.js flow (high level) 1. Subscribe audio track from LiveKit participant. 2. Stream audio frames to Speechmatics for diarization. 3. For each diarized segment: ◦ Extract audio chunk (based on timestamps). ◦ Send chunk to emotion service -> receive emotion label + confidence. ◦ Send chunk to speaker-ID service -> receive identity or null. ◦ Save {speakerId, identity, emotion, text, ts} to session memory. 1. On detection of direct-address trigger (wake word, mention, push-to-talk): ◦ Build prompt including recent context + speaker identity + emotion cues, e.g.: "S1 (angry): '...'. S2 (neutral): '...'. Respond calmly to S1." • Call LLM/TTS to generate response and play it. • Clear or retain context per session policy. Example prompt snippet to leverage emotion • "Speaker S1 appears angry (confidence 0.9) and says: '...'. Respond empathetically and keep tone calm." Operational tips • Batch short segments (1–3s) to reduce churn, or use sentence-level segmentation from STT. • Use confidence thresholds to ignore low-confidence emotion matches. • Log emotion+speaker decisions for auditing and tuning. • If latency is critical, run the emotion model locally (GPU/CPU) to avoid network hops. If you want, I can: • Provide a concrete Node.js code sketch wiring LiveKit audio -> Speechmatics streaming -> emotion API call -> speaker-ID call -> commitUserTurn logic. • Draft LLM prompt templates that include emotion and identity metadata for calmer/responsive behavior. Which would you like next: the Node.js code sketch or the prompt templates?