Skip to content

adding attributes or metadata to AccessToken is not reflected on python voice agent. Returns empty values. #452

Open
@bidah

Description

@bidah

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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions