Open
Description
Select which package(s) are affected
livekit-server-sdk
Describe the bug
What I'm expecting
to create an AccessToken with metadata or attributes and get those back on the voice agent python script on the context.
What happens instead
context gets back with empty values for context and metadata
Reproduction
next.js route with create token that has metadata and attributes set.
import { RoomAgentDispatch, RoomConfiguration } from '@livekit/protocol'
import {
AccessToken,
AccessTokenOptions,
VideoGrant,
} from "livekit-server-sdk";
import { NextResponse } from "next/server";
// NOTE: you are expected to define the following environment variables in `.env.local`:
const API_KEY = process.env.LIVEKIT_API_KEY;
const API_SECRET = process.env.LIVEKIT_API_SECRET;
const LIVEKIT_URL = process.env.LIVEKIT_URL;
// don't cache the results
export const revalidate = 0;
export type ConnectionDetails = {
serverUrl: string;
roomName: string;
participantName: string;
participantToken: string;
};
export async function GET() {
try {
if (LIVEKIT_URL === undefined) {
throw new Error("LIVEKIT_URL is not defined");
}
if (API_KEY === undefined) {
throw new Error("LIVEKIT_API_KEY is not defined");
}
if (API_SECRET === undefined) {
throw new Error("LIVEKIT_API_SECRET is not defined");
}
// Generate participant token
const participantIdentity = `voice_assistant_user_${Math.floor(Math.random() * 10_000)}`;
const roomName = `voice_assistant_room_${Math.floor(Math.random() * 10_000)}`;
const participantToken = await createParticipantToken(
{ identity: participantIdentity },
roomName,
);
// Return connection details
const data: ConnectionDetails = {
serverUrl: LIVEKIT_URL,
roomName,
participantToken: participantToken,
participantName: participantIdentity,
};
const headers = new Headers({
"Cache-Control": "no-store",
});
return NextResponse.json(data, { headers });
} catch (error) {
if (error instanceof Error) {
console.error(error);
return new NextResponse(error.message, { status: 500 });
}
}
}
function createParticipantToken(
userInfo: AccessTokenOptions,
roomName: string
) {
const at = new AccessToken(API_KEY, API_SECRET, {
attributes: {
testing: "testing",
},
metadata: "this is a testing test",
...userInfo,
ttl: "15m",
});
at.attributes = {
testing: "testing",
};
const grant: VideoGrant = {
room: roomName,
roomJoin: true,
canPublish: true,
canPublishData: true,
canSubscribe: true,
};
at.addGrant(grant);
at.metadata = 'test';
at.identity = 'identity test';
return at.toJwt();
}
python agent:
import logging
from dotenv import load_dotenv
from livekit.agents import (
AutoSubscribe,
JobContext,
JobProcess,
WorkerOptions,
cli,
llm,
metrics,
)
from livekit.agents.pipeline import VoicePipelineAgent
from livekit.plugins import (
cartesia,
openai,
deepgram,
noise_cancellation,
silero,
turn_detector,
)
load_dotenv(dotenv_path=".env.local")
logger = logging.getLogger("voice-agent")
def prewarm(proc: JobProcess):
proc.userdata["vad"] = silero.VAD.load()
async def entrypoint(ctx: JobContext):
logger.info(f"job context: {ctx}")
logger.info(f"job metadata: {ctx.__dict__}")
logger.info(f"job info: {ctx._info}")
initial_ctx = llm.ChatContext().append(
role="system",
text=(
"You are a voice assistant created by Expo AI Chatbot. Your interface with users will be voice. "
"You should use short and concise responses, and avoiding usage of unpronouncable punctuation. "
),
)
logger.info(f"connecting to room {ctx.room.name}")
await ctx.connect(auto_subscribe=AutoSubscribe.AUDIO_ONLY)
# Wait for the first participant to connect
participant = await ctx.wait_for_participant()
logger.info(f"starting voice assistant for participant {participant.identity}")
# This project is configured to use Deepgram STT, OpenAI LLM and Cartesia TTS plugins
# Other great providers exist like Cerebras, ElevenLabs, Groq, Play.ht, Rime, and more
# Learn more and pick the best one for your app:
# https://docs.livekit.io/agents/plugins
agent = VoicePipelineAgent(
vad=ctx.proc.userdata["vad"],
stt=deepgram.STT(),
llm=openai.LLM(model="gpt-4o-mini"),
tts=cartesia.TTS(),
# use LiveKit's transformer-based turn detector
turn_detector=turn_detector.EOUModel(),
# minimum delay for endpointing, used when turn detector believes the user is done with their turn
min_endpointing_delay=0.5,
# maximum delay for endpointing, used when turn detector does not believe the user is done with their turn
max_endpointing_delay=5.0,
# enable background voice & noise cancellation, powered by Krisp
# included at no additional cost with LiveKit Cloud
noise_cancellation=noise_cancellation.BVC(),
chat_ctx=initial_ctx,
)
usage_collector = metrics.UsageCollector()
@agent.on("metrics_collected")
def on_metrics_collected(agent_metrics: metrics.AgentMetrics):
metrics.log_metrics(agent_metrics)
usage_collector.collect(agent_metrics)
agent.start(ctx.room, participant)
# The agent should be polite and greet the user when it joins :)
# await agent.say("Hey, how can I help you today?", allow_interruptions=True)
await agent.say("", allow_interruptions=True)
if __name__ == "__main__":
cli.run_app(
WorkerOptions(
entrypoint_fnc=entrypoint,
prewarm_fnc=prewarm,
),
)
Logs
what I get back on python agent is empty values
logs:
2025-03-31 21:29:04,096 - INFO voice-agent: {'_proc': <livekit.agents.job.JobProcess object at 0x1214fd550>, '_info': RunningJobInfo(accept_arguments=JobAcceptArguments(name='', identity='agent-AJ_YfqvVwqRqNZf', metadata='', attributes=None), job=id: "AJ_YfqvVwqRqNZf"
room {
all logs:
2025-03-31 21:29:04,027 - INFO livekit.agents - initializing job process {"pid": 41304}
2025-03-31 21:29:04,094 - INFO livekit.agents - job process initialized {"pid": 41304, "job_id": "AJ_YfqvVwqRqNZf"}
2025-03-31 21:29:04,095 - DEBUG asyncio - Using selector: KqueueSelector {"pid": 41304, "job_id": "AJ_YfqvVwqRqNZf"}
2025-03-31 21:29:04,096 - INFO voice-agent - job context: <livekit.agents.job.JobContext object at 0x1214ffb60> {"pid": 41304, "job_id": "AJ_YfqvVwqRqNZf"}
2025-03-31 21:29:04,096 - INFO voice-agent - job metadata: {'_proc': <livekit.agents.job.JobProcess object at 0x1214fd550>, '_info': RunningJobInfo(accept_arguments=JobAcceptArguments(name='', identity='agent-AJ_YfqvVwqRqNZf', metadata='', attributes=None), job=id: "AJ_YfqvVwqRqNZf"
room {
sid: "RM_WnDcaQuz6Gnp"
name: "voice_assistant_room_1428"
empty_timeout: 300
creation_time: 1743467342
enabled_codecs {
mime: "video/H264"
}
enabled_codecs {
mime: "video/VP8"
}
enabled_codecs {
mime: "video/VP9"
}
enabled_codecs {
mime: "video/AV1"
}
enabled_codecs {
mime: "audio/red"
}
enabled_codecs {
mime: "audio/opus"
}
version {
unix_micro: 1743467343031154
}
departure_timeout: 20
creation_time_ms: 1743467342871
}
state {
status: JS_RUNNING
started_at: 1743467343232851782
updated_at: 1743467343232851782
}
, url='wss://expo-ai-chatbot-jq0ubnkf.livekit.cloud', token='eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NDM0NzA5NDMsImlzcyI6IkFQSWRjckFjNnkzdDdvaSIsImtpbmQiOiJhZ2VudCIsIm5iZiI6MTc0MzQ2NzM0Mywic3ViIjoiYWdlbnQtQUpfWWZxdlZ3cVJxTlpmIiwidmlkZW8iOnsiYWdlbnQiOnRydWUsImNhblB1Ymxpc2giOnRydWUsImNhblB1Ymxpc2hEYXRhIjp0cnVlLCJjYW5TdWJzY3JpYmUiOnRydWUsImNhblN1YnNjcmliZU1ldHJpY3MiOmZhbHNlLCJjYW5VcGRhdGVPd25NZXRhZGF0YSI6dHJ1ZSwicm9vbSI6InZvaWNlX2Fzc2lzdGFudF9yb29tXzE0MjgiLCJyb29tSm9pbiI6dHJ1ZX19.oeYUOovDljvnOV4ofIpk6D7A4p35Bl-aabCDe-znx7s', worker_id='AW_pJAc48PgMPtA'), '_room': rtc.Room(sid=unknown, name=voice_assistant_room_1428, metadata=, connection_state=CONN_DISCONNECTED), '_on_connect': <function _JobProc._start_job.<locals>._on_ctx_connect at 0x12159cb80>, '_on_shutdown': <function _JobProc._start_job.<locals>._on_ctx_shutdown at 0x12159d260>, '_shutdown_callbacks': [], '_participant_entrypoints': [], '_participant_tasks': {}, '_inf_executor': <livekit.agents.ipc.job_proc_lazy_main._InfClient object at 0x1214fde80>} {"pid": 41304, "job_id": "AJ_YfqvVwqRqNZf"}
2025-03-31 21:29:04,096 - INFO voice-agent - job info: RunningJobInfo(accept_arguments=JobAcceptArguments(name='', identity='agent-AJ_YfqvVwqRqNZf', metadata='', attributes=None), job=id: "AJ_YfqvVwqRqNZf"
room {
sid: "RM_WnDcaQuz6Gnp"
name: "voice_assistant_room_1428"
empty_timeout: 300
creation_time: 1743467342
enabled_codecs {
mime: "video/H264"
}
enabled_codecs {
mime: "video/VP8"
}
enabled_codecs {
mime: "video/VP9"
}
enabled_codecs {
mime: "video/AV1"
}
enabled_codecs {
mime: "audio/red"
}
enabled_codecs {
mime: "audio/opus"
}
version {
unix_micro: 1743467343031154
}
departure_timeout: 20
creation_time_ms: 1743467342871
}
state {
status: JS_RUNNING
started_at: 1743467343232851782
updated_at: 1743467343232851782
}
, url='wss://expo-ai-chatbot-jq0ubnkf.livekit.cloud', token='eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NDM0NzA5NDMsImlzcyI6IkFQSWRjckFjNnkzdDdvaSIsImtpbmQiOiJhZ2VudCIsIm5iZiI6MTc0MzQ2NzM0Mywic3ViIjoiYWdlbnQtQUpfWWZxdlZ3cVJxTlpmIiwidmlkZW8iOnsiYWdlbnQiOnRydWUsImNhblB1Ymxpc2giOnRydWUsImNhblB1Ymxpc2hEYXRhIjp0cnVlLCJjYW5TdWJzY3JpYmUiOnRydWUsImNhblN1YnNjcmliZU1ldHJpY3MiOmZhbHNlLCJjYW5VcGRhdGVPd25NZXRhZGF0YSI6dHJ1ZSwicm9vbSI6InZvaWNlX2Fzc2lzdGFudF9yb29tXzE0MjgiLCJyb29tSm9pbiI6dHJ1ZX19.oeYUOovDljvnOV4ofIpk6D7A4p35Bl-aabCDe-znx7s', worker_id='AW_pJAc48PgMPtA') {"pid": 41304, "job_id": "AJ_YfqvVwqRqNZf"}
2025-03-31 21:29:04,096 - INFO voice-agent - connecting to room voice_assistant_room_1428 {"pid": 41304, "job_id": "AJ_YfqvVwqRqNZf"}
2025-03-31 21:29:04,097 - INFO livekit - livekit_api::signal_client::signal_stream:96:livekit_api::signal_client::signal_stream - connecting to wss://expo-ai-chatbot-jq0ubnkf.livekit.cloud/rtc?sdk=python&protocol=15&auto_subscribe=0&adaptive_stream=0&version=0.22.0&access_token=... {"pid": 41304, "job_id": "AJ_YfqvVwqRqNZf"}
2025-03-31 21:29:04,499 - DEBUG livekit - rustls::anchors:150:rustls::anchors - add_parsable_certificates processed 159 valid and 0 invalid certs {"pid": 41304, "job_id": "AJ_YfqvVwqRqNZf"}
2025-03-31 21:29:04,499 - DEBUG livekit - tokio_tungstenite::tls::encryption::rustls:103:tokio_tungstenite::tls::encryption::rustls - Added 159/159 native root certificates (ignored 0) {"pid": 41304, "job_id": "AJ_YfqvVwqRqNZf"}
2025-03-31 21:29:04,500 - DEBUG livekit - rustls::client::hs:73:rustls::client::hs - No cached session for DnsName("expo-ai-chatbot-jq0ubnkf.livekit.cloud") {"pid": 41304, "job_id": "AJ_YfqvVwqRqNZf"}
2025-03-31 21:29:04,500 - DEBUG livekit - rustls::client::hs:132:rustls::client::hs - Not resuming any session {"pid": 41304, "job_id": "AJ_YfqvVwqRqNZf"}
2025-03-31 21:29:04,599 - DEBUG livekit - rustls::client::hs:615:rustls::client::hs - Using ciphersuite TLS13_AES_128_GCM_SHA256 {"pid": 41304, "job_id": "AJ_YfqvVwqRqNZf"}
2025-03-31 21:29:04,599 - DEBUG livekit - rustls::client::tls13:142:rustls::client::tls13 - Not resuming {"pid": 41304, "job_id": "AJ_YfqvVwqRqNZf"}
2025-03-31 21:29:04,599 - DEBUG livekit - rustls::client::tls13:381:rustls::client::tls13 - TLS1.3 encrypted extensions: [] {"pid": 41304, "job_id": "AJ_YfqvVwqRqNZf"}
2025-03-31 21:29:04,599 - DEBUG livekit - rustls::client::hs:472:rustls::client::hs - ALPN protocol is None {"pid": 41304, "job_id": "AJ_YfqvVwqRqNZf"}
2025-03-31 21:29:04,824 - DEBUG livekit - tungstenite::handshake::client:95:tungstenite::handshake::client - Client handshake done. {"pid": 41304, "job_id": "AJ_YfqvVwqRqNZf"}
2025-03-31 21:29:05,515 - INFO voice-agent - starting voice assistant for participant identity test {"pid": 41304, "job_id": "AJ_YfqvVwqRqNZf"}
2025-03-31 21:29:05,758 - DEBUG livekit.agents - http_session(): creating a new httpclient ctx {"pid": 41304, "job_id": "AJ_YfqvVwqRqNZf"}
2025-03-31 21:29:06,680 - DEBUG livekit.agents.pipeline - speech playout finished {"speech_id": "f31eb22eebe0", "interrupted": false, "pid": 41304, "job_id": "AJ_YfqvVwqRqNZf"}
2025-03-31 21:29:34,840 - DEBUG livekit - tungstenite::protocol:666:tungstenite::protocol - Received close frame: Some(CloseFrame { code: Normal, reason: "" }) {"pid": 41304, "job_id": "AJ_YfqvVwqRqNZf"}
2025-03-31 21:29:34,842 - DEBUG livekit - tungstenite::protocol:683:tungstenite::protocol - Replying to close with Frame { header: FrameHeader { is_final: true, rsv1: false, rsv2: false, rsv3: false, opcode: Control(Close), mask: None }, payload: [3, 232] } {"pid": 41304, "job_id": "AJ_YfqvVwqRqNZf"}
2025-03-31 21:29:34,846 - INFO livekit.agents - process exiting {"reason": "", "pid": 41304, "job_id": "AJ_YfqvVwqRqNZf"}
2025-03-31 21:29:34,845 - DEBUG livekit.agents - shutting down job task {"reason": "", "user_initiated": false, "pid": 41304, "job_id": "AJ_YfqvVwqRqNZf"}
2025-03-31 21:29:34,846 - WARNING livekit - livekit::rtc_engine:446:livekit::rtc_engine - received session close: "signal client closed: \"stream closed\"" UnknownReason Resume {"pid": 41304, "job_id": "AJ_YfqvVwqRqNZf"}
2025-03-31 21:29:34,852 - INFO livekit - livekit::room:1191:livekit::room - disconnected from room with reason: RoomClosed {"pid": 41304, "job_id": "AJ_YfqvVwqRqNZf"}
2025-03-31 21:29:34,860 - DEBUG livekit.agents - http_session(): closing the httpclient ctx {"pid": 41304, "job_id": "AJ_YfqvVwqRqNZf"}
System Info
System:
OS: macOS 14.3.1
CPU: (12) arm64 Apple M2 Pro
Memory: 172.00 MB / 16.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 20.11.1 - ~/.nvm/versions/node/v20.11.1/bin/node
Yarn: 1.22.21 - /opt/homebrew/bin/yarn
npm: 10.2.4 - ~/.nvm/versions/node/v20.11.1/bin/npm
pnpm: 9.5.0 - ~/.nvm/versions/node/v20.11.1/bin/pnpm
bun: 1.0.30 - ~/.bun/bin/bun
Watchman: 2024.10.14.00 - /opt/homebrew/bin/watchman
LiveKit server version
LiveKit cloud
Severity
annoyance
Additional Information
No response
Metadata
Metadata
Assignees
Labels
No labels