c
hi
✅ 1
t
Thanks for reaching out, Arun. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Hi — thanks for reaching out! How can I help you today? If you have an issue or a question, a few details that help me assist you faster: • What are you trying to do (e.g., join a room, set up a server, debug a client)? • Any error messages, screenshots, or room/project IDs you can share? • Which SDK or platform are you using (Web/React Native/iOS/Android/Server)? Tell me whatever you have and I’ll guide you through the next steps.
c
how can i creta chat bot in liveiit agent give code
t
Thanks — I can help you build a chatbot with LiveKit Agents. Quick clarifying questions so I give you the right code and steps: • Do you want a voice chatbot (speech in/out) or text-only chat? • Which language do you prefer: Python or Node.js? • Where will it run: web client, mobile, or as a server-side/telephony agent? • Do you already have a LiveKit project and API key/secret? If you want to start right away, here’s a concise Python voice-enabled example that joins a room, transcribes speech, calls an LLM for a reply, and speaks the reply back. Save this as
agent.py
and adjust environment variables as noted.
Copy code
# Minimal voice-enabled Agent example (Python)
import logging
from dotenv import load_dotenv
from livekit.agents import AgentSession, JobContext, AgentServer, cli, inference
from livekit.plugins import silero

load_dotenv()
logger = logging.getLogger("livekit-agent")
logger.setLevel(<http://logging.INFO|logging.INFO>)

server = AgentServer()

def prewarm(proc):
    proc.userdata["vad"] = silero.VAD.load()

server.setup_fnc = prewarm

@server.rtc_session()
async def on_session(ctx: JobContext):
    <http://logger.info|logger.info>("Starting agent session")
    session = AgentSession(
        stt=inference.STT(model="deepgram/nova-2"),
        llm=inference.LLM(model="openai/gpt-4o"),
        tts=inference.TTS(model="cartesia/sonic-3"),
        vad=ctx.proc.userdata["vad"],
        preemptive_generation=True,
    )

    await session.start(ctx.room)
    await ctx.connect()
    await session.generate_reply(instructions="Greet the participant and ask how you can help.")

if __name__ == "__main__":
    cli.run_app(server)
.env (example)
Copy code
LIVEKIT_API_KEY=your_api_key
LIVEKIT_API_SECRET=your_api_secret
LIVEKIT_URL=<wss://your-project.livekit.cloud>
Commands: • Install deps:
pip install "livekit-agents[silero]" python-dotenv
• Run locally:
python agent.py console
• Deploy:
lk agent create --agent-file agent.py
If you’d rather do text-only, remove `stt`/`tts` (set them to
None
) and use
session.chat_ctx.add_message()
plus
session.generate_reply()
. Tell me which language and mode (voice or text) you want and whether you’d like a Node.js example or a frontend integration — I’ll send a tailored code sample and next steps. Sources: Simple Call Answering Agent | LiveKit Documentation
c
i need ot only chat bot not voice
t
Great — I'll help you build a text-only chatbot (no voice). Quick question so I give you the right code and steps: • Which programming language do you prefer: Python or Node.js? • Where will the bot run: server-side (as an agent process) or integrated into a web/mobile client? • Do you already have a LiveKit project with API key/secret and a room to join? Once you tell me the language and runtime, I’ll send a complete example (setup, code, and how to run/deploy) for a text-only LiveKit Agent chatbot.
c
pyhton
t
Great — below is a simple, ready-to-run Python example for a text-only LiveKit Agent chatbot (no voice). It shows how to disable audio, accept text input from participants, generate replies with an LLM, and publish text responses back into the room. Before you start, quick checklist: • You have a LiveKit project and API key/secret. • Python 3.9+ installed. • You’ll set environment variables for LiveKit connection details. 1) Install dependencies
Copy code
pip install "livekit-agents" python-dotenv
2) Create a
.env
file in the same folder:
Copy code
LIVEKIT_API_KEY=your_api_key
LIVEKIT_API_SECRET=your_api_secret
LIVEKIT_URL=<wss://your-project.livekit.cloud>
3) Save this as `text_agent.py`:
Copy code
# text_agent.py
import logging
from dotenv import load_dotenv
from livekit.agents import AgentServer, AgentSession, Agent, JobContext, cli, inference
from <http://livekit.agents.io|livekit.agents.io> import room_io

load_dotenv()
logging.basicConfig(level=<http://logging.INFO|logging.INFO>)
logger = logging.getLogger("text_agent")

server = AgentServer()

class TextOnlyAgent(Agent):
    def __init__(self):
        super().__init__(instructions="You are a helpful text-only assistant.")

    async def on_enter(self):
        # Optional greeting when the agent joins the room
        await self.session.say("Hello! I am a text-only assistant. How can I help you today?")

@server.rtc_session()
async def on_session(ctx: JobContext):
    <http://logger.info|logger.info>("Starting text-only agent session")

    # Create an AgentSession with no STT/TTS; use an LLM for responses
    session = AgentSession(
        stt=None,            # no speech-to-text
        tts=None,            # no text-to-speech
        llm=inference.LLM(model="openai/gpt-4o"),  # replace with your preferred model or remove
    )

    # RoomOptions to ensure no audio is used and text input/output enabled
    room_opts = room_io.RoomOptions(
        audio_input=False,
        audio_output=False,
        text_input=True,
        text_output=True,
    )

    # Start the session with the TextOnlyAgent
    await session.start(
        agent=TextOnlyAgent(),
        room=ctx.room,
        room_options=room_opts,
    )

    # Connect the agent to the LiveKit room
    await ctx.connect()

    # Keep running until the session ends
    await session.wait_until_closed()
4) How it works (short) •
stt=None
and
tts=None
disable audio processing. •
room_io.RoomOptions
disables audio publishing and enables text input/output. • The agent receives participant text via LiveKit text streams (topic
lk.chat
) and publishes responses on the transcription/text stream (
lk.transcription
). •
session.say()
sends a text reply into the room. 5) Run locally for testing
Copy code
python text_agent.py console
This runs the agent in console mode. Use a client (web or other) to join the same LiveKit room and send text messages; the agent will read them and respond as text. 6) Client notes • On the client, listen for the text stream for agent replies (topic
lk.transcription
). • When participants send messages, they appear on the
lk.chat
text stream; the agent can read and respond to those. Would you like: • A minimal web client example (JavaScript) that sends/receives text to/from the agent? • A version that uses only rule-based responses (no LLM)? • Help picking or configuring an LLM model and API keys? Tell me which next step you prefer and I’ll provide that code. Sources: Text and transcriptions | LiveKit Documentation | How to build an agent with speech-to-text input and text-only output | Live... | Transcriber | LiveKit Documentation
c
font end react
t
Great — below is a concise React frontend that connects to a LiveKit room, sends text messages to your text-only Python agent, and displays incoming messages from participants and the agent (agent replies arrive on the
lk.transcription
text stream). Summary of what you’ll get: • Install steps • Token generation note (server-side) • A simple React component that: ◦ Connects to LiveKit with a token ◦ Sends text messages (published to
lk.chat
) ◦ Listens for text streams and shows agent replies (
lk.transcription
) and participant chat Prerequisites • Your Python text-only agent is running and joined the same LiveKit room. • You have a server endpoint that returns a LiveKit join token for the client (short-lived access token). The frontend needs that token to connect. If you want an example token endpoint, I can provide one separately. 1) Install dependencies
Copy code
npm install livekit-client
# or with yarn
# yarn add livekit-client
2) Minimal React component (functional) Save as e.g. `ChatRoom.jsx`:
Copy code
import React, { useEffect, useState, useRef } from "react";
import { connect, Room, LocalTrackPublication } from "livekit-client";

export default function ChatRoom({ tokenUrl, roomName, userName }) {
  const [room, setRoom] = useState(null);
  const [connected, setConnected] = useState(false);
  const [messages, setMessages] = useState([]); // {sender, text, topic}
  const inputRef = useRef();

  // Helper to append message
  function pushMessage(sender, text, topic = "lk.chat") {
    setMessages((m) => [...m, { sender, text, topic, id: Date.now() + Math.random() }]);
  }

  // Connect to LiveKit on mount
  useEffect(() => {
    let r;
    async function init() {
      try {
        // Request a token from your server
        const resp = await fetch(`${tokenUrl}?room=${encodeURIComponent(roomName)}&name=${encodeURIComponent(userName)}`);
        const { token, url } = await resp.json(); // token and optional custom LiveKit url
        r = await connect(url || undefined, token, {
          // Avoid publishing audio/video for text-only client
          publishDefaults: { audio: false, video: false },
        });
        setRoom(r);
        setConnected(true);

        // Listen for incoming text streams
        r.on("textMessage", (msg, participant) => {
          // This event is for chat messages sent via the text API (legacy)
          // Some clients use explicit text streams; handle both.
          const sender = participant?.identity || msg.participantSid || "unknown";
          pushMessage(sender, msg.text || msg, "lk.chat");
        });

        // Register text stream handlers (for stream topics like lk.transcription)
        r.on("textStreamPublished", (stream, participant) => {
          // When a new text stream is published, attach a reader
          attachTextStream(stream, participant);
        });

        // Attach any existing text streams (in case agent already published)
        r.participants.forEach((p) => {
          p.getPublishedTextTracks?.()?.forEach((track) => attachTextStream(track, p));
        });

        // Local participant also may have text streams published
        r.localParticipant.getPublishedTextTracks?.()?.forEach((track) => attachTextStream(track, r.localParticipant));
      } catch (err) {
        console.error("connect error", err);
      }
    }
    init();

    return () => {
      if (r) r.disconnect();
    };
  }, [tokenUrl, roomName, userName]);

  // Attach text stream reader function
  async function attachTextStream(track, participant) {
    try {
      // livekit-client exposes a reader to consume text stream messages
      const reader = await track.createReader();
      (async () => {
        for await (const msg of reader) {
          // msg will be a string or object depending on publisher
          const text = typeof msg === "string" ? msg : JSON.stringify(msg);
          const sender = participant?.identity || "agent";
          const topic = track.topic || "lk.transcription";
          pushMessage(sender, text, topic);
        }
      })();
    } catch (e) {
      console.warn("failed to attach text stream", e);
    }
  }

  // Send a text message into the room (published to lk.chat)
  async function sendMessage() {
    if (!room) return;
    const text = inputRef.current.value.trim();
    if (!text) return;
    // Publish via LiveKit text message API
    // The livekit-client provides publishChatMessage
    try {
      room.localParticipant.publishChatMessage(text);
      pushMessage(userName, text, "lk.chat");
      inputRef.current.value = "";
    } catch (e) {
      console.error("send failed", e);
    }
  }

  return (
    <div style={{ maxWidth: 720, margin: "0 auto" }}>
      <h3>Text Chat Room: {roomName}</h3>
      <div style={{ border: "1px solid #ddd", padding: 12, height: 400, overflow: "auto" }}>
        {messages.map((m) => (
          <div key={m.id} style={{ marginBottom: 8 }}>
            <strong>{m.sender}</strong> <em style={{ color: "#666", fontSize: 12 }}>[{m.topic}]</em>
            <div>{m.text}</div>
          </div>
        ))}
      </div>

      <div style={{ marginTop: 8 }}>
        <input ref={inputRef} placeholder="Type a message..." style={{ width: "70%" }} onKeyDown={(e) => { if (e.key === "Enter") sendMessage(); }} />
        <button onClick={sendMessage} style={{ marginLeft: 8 }}>Send</button>
      </div>

      <div style={{ marginTop: 12 }}>
        <strong>Status:</strong> {connected ? "connected" : "disconnected"}
      </div>
    </div>
  );
}
3) How it works — quick notes • The client requests a join token from your server at
tokenUrl
. Your server should return JSON { token, url? } where
token
is a LiveKit access token and
url
is the LiveKit websocket URL if needed. • The component disables audio/video publishing (
publishDefaults
) so the client stays text-only. • Messages typed by the user are sent with
room.localParticipant.publishChatMessage(text)
. Those appear on the
lk.chat
stream for the agent to read. • The agent publishes replies on the
lk.transcription
text stream (or
lk.chat
depending on how it's implemented). The frontend listens for text streams and displays messages. We listen both to the legacy
textMessage
event and text stream publications to cover both patterns. • Agent replies typically show up on a text stream with topic
lk.transcription
. The code attaches readers for published text streams and pushes incoming messages into local state for display. 4) Server token endpoint (brief) Your frontend cannot hold the LiveKit API secret. Create a small server endpoint that issues tokens (example not included here yet). The endpoint should return: { "token": "<access_token>", "url": "wss://<your-livekit-host>" } If you want, I can provide a minimal Python/Node token-creation example next. 5) Next steps I can provide • A minimal server example (Python/Node) that issues LiveKit tokens. • A refined UI that separates agent messages visually and highlights the agent. • Instructions on how to ensure your Python agent publishes replies on
lk.transcription
(I can show sample code for session.say() or direct text stream publishing). Which of those do you want next — the token endpoint, a polished UI, or confirmation that the agent is publishing on
lk.transcription
?
Thanks — I can hear your frustration, and you’re right to call this out. I’ll address what happened, why your workaround is correct, and the practical next steps you can take. Acknowledgement • You upgraded to livekit-agents 1.5.0 and then had to manually register a handler for the
lk.chat
text stream so the agent receives chat messages. That’s unexpected behavior and it’s reasonable to consider it a regression if it previously worked automatically. Why this happens (short) • The AgentSession normally subscribes to text input on
lk.chat
when text input is enabled (via
RoomInputOptions
/
text_input=True
). That lets the agent automatically react to incoming chat. • In 1.5.0 the automatic hookup of the
lk.chat
handler appears to have been removed or changed (intentional change or regression). Because the agent no longer automatically attaches the text-stream handler in your environment, you need to register it yourself — exactly like your workaround does. Why your workaround is correct • Your `_register_lk_chat_text_handler`: ◦ Ignores messages from the local participant (prevents echo). ◦ Reads the text from the text stream and strips it. ◦ Interrupts the current session and calls
session.generate_reply(user_input=text)
. ◦ Runs the reader as a background task. • That matches the intended behavior the embedded handler used to provide. It’s a safe and appropriate way to restore the original behavior until the root cause is resolved. Practical next steps 1. Short-term: keep your manual handler in place (your code is fine). It restores expected behavior immediately and is robust. 2. Verify config: confirm your
RoomOptions
still have
text_input=True
(that’s default but good to check), so you’re not accidentally opting out elsewhere. 3. If you can, test on the previous livekit-agents version (the one that worked) to confirm this is an upgrade regression in your environment. That helps isolate whether it’s a breaking change or a bug. 4. Share minimal repro: if you want me to help file a clear bug report, I can help craft a minimal reproduction snippet showing the difference between pre-1.5.0 and 1.5.0 behavior (your snippet + a small room setup is perfect for that). What I can help with next • I can produce a slightly improved/cleaned version of your handler (non-blocking loop, graceful shutdown), if you’d like. • I can help build a minimal repro and a concise issue report to post to the agents repo so maintainers can investigate faster. • I can check your `AgentSession`/`RoomOptions` code to confirm nothing else disables automatic text handling. If you want the improved handler now, here’s a small cleanup (keeps same behavior but uses an async loop and logs errors):
Copy code
def _register_lk_chat_text_handler(room: rtc.Room, session: AgentSession) -> None:
    local_identity = room.local_participant.identity

    def on_text_stream(reader: rtc.TextStreamReader, participant_identity: str) -> None:
        async def consume() -> None:
            if participant_identity == local_identity:
                return
            try:
                text = (await reader.read_all()).strip()
            except Exception:
                logger.exception("Failed to read lk.chat text stream")
                return
            if not text:
                return
            try:
                await session.interrupt()
            except RuntimeError:
                # session may already be closed or interrupt not allowed
                pass
            # Fire-and-forget reply generation
            asyncio.create_task(session.generate_reply(user_input=text))

        asyncio.create_task(consume())

    room.register_text_stream_handler(TOPIC_CHAT, on_text_stream)
Tell me which of the next steps you want: • I’ll help prepare a minimal repro + issue text for the GitHub repo. • I’ll review your AgentSession/RoomOptions for anything suspicious. • I’ll provide a more fully-featured handler (e.g., streaming reads, rate limiting, or message batching). You did the right thing by adding that handler — let me know how you want to proceed and I’ll help you get this fixed or reported cleanly. Sources: Text and transcriptions | LiveKit Documentation | examples/other | livekit/livekit_composite | DeepWiki, with interactive elements