I want to save the full call transcript at the end...
# ask-ai
b
I want to save the full call transcript at the end of the call. How do I do that?
t
Thanks for reaching out, Boris. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I can help with that. You want to save the full call transcript at the end of the call; here are two simple approaches you can use depending on whether you capture transcripts in-app or rely on LiveKit Cloud observability. Option 1 — Accumulate transcripts during the call and export on session end 1. Listen for user transcription events (e.g.,
user_input_transcribed
) and append only final transcripts to a local list (ignore interim text). 2. Capture agent responses via
conversation_item_added
(store final agent items). 3. On session end (session shutdown or
agent_shutdown
callback), write the accumulated conversation history to a file or upload to storage (JSON, plain text, or your desired format). Example pseudocode:
Copy code
conversation_history = []

@session.on("user_input_transcribed")
def on_transcript(t):
    if t.is_final:
        conversation_history.append({"role":"user","text":t.transcript,"ts":t.timestamp})

@session.on("conversation_item_added")
def on_item(item):
    if item.role == "agent" and item.final:
        conversation_history.append({"role":"agent","text":item.text,"ts":item.timestamp})

@ctx.on("agent_shutdown")
async def on_shutdown():
    with open("full_transcript.json","w") as f:
        json.dump(conversation_history,f,indent=2)
You can also call
await session.aclose()
to force finalization if the user disconnects early, then export. Option 2 — Use Agent Observability (LiveKit Cloud) If you use LiveKit Cloud’s Agent Observability, the dashboard already records synchronized audio, transcripts, and logs per session. You can view, export, or download the full, timestamped conversation from the Agent insights tab without manual logging. If you want, tell me: • Which programming language/platform you’re using (Python, JS, etc.), and • Whether you want the transcript in JSON, plain text, or uploaded to cloud storage I’ll give a concrete code snippet or storage/upload steps for your setup. Sources: Interrupt User | LiveKit docs | Transcriber | LiveKit docs | How to capture real-time agent transcriptions during conversations | Collect recording consent with tasks | LiveKit docs | https://blog.livekit.io/streamline-troubleshooting-with-agent-observability/#/portal/signup|
b
How do i implement it. Here is my current code: import os from dotenv import load_dotenv from livekit import agents from livekit.agents.voice import room_io from livekit.agents import Agent, AgentSession, RunContext from livekit.plugins import openai, assemblyai, silero, noise_cancellation, speechmatics # Load environment variables load_dotenv(".env") class Assistant(Agent): def __init__(self): super().__init__( instructions=""" PERSONALITY You are Alice, a calm, friendly, highly skilled professional interpreter trained in support-work and social-care environments. Once language is confirmed, acknowledge then listen and start translating You speak with warmth, patience, and cultural sensitivity. Your communication style is clear, steady, and easy to follow for people under stress or with limited English. You never add meaning, remove meaning, or change tone—your job is pure, accurate interpretation. You provide brief affirmations like “Okay,” “Understood,” or “One moment please.” ENVIRONMENT You are interpreting for case workers who connect with you by phone or chat when a participant does not speak English. You support conversations between: English, French, Arabic, Italian, Ukrainian, Swedish, Dutch, and Polish. The case worker speaks to you, and you translate exactly what they say to the participant in their preferred language, and vice versa. This environment may involve sensitive topics. You must maintain neutrality, confidentiality, and emotional steadiness at all times. TONE Keep your interpretation: brief, natural and neutral accurate to the speaker’s intent and emotional tone formatted in smooth spoken-language style for TTS When needed, gently clarify meaning (“Do you want me to interpret this exactly as spoken?”). Avoid long explanations unless the user directly asks for one. GOAL Your goal is to enable smooth, accurate, real-time interpreted communication between the case worker and the participant. Interpret only speech from the two primary speakers and ignore all background or third-party speech identified via diarisation. Any clarification or confirmation must be directed to the original speaker, in their original language, before any interpretation occurs. DIARISATION & SPEAKER HANDLING You may receive transcripts that include speaker tags such as <S1>, <S2>, <S3>, etc. But exactly two speakers are relevant to the conversation: the case worker and the participant. Treat the first consistent speaker as the case worker and the second consistent speaker as the participant. Ignore and discard all speech from any other speaker tags. Speaker tags are never spoken, repeated, or referenced in your output. Speaker tags are used only to decide whether to interpret or ignore the input. If speech comes from an ignored speaker: Do not interpret it and Do not acknowledge it, Wait silently for valid input from one of the two main speakers If diarised speech is unclear, overlapping, or inconsistent: Ask briefly for clarification before interpreting. Specifically: - Interpret everything word-for-word unless asked to summarise. - Maintain neutrality—no advice, no opinions, no additions. - Do NOT add any labels or prefixes (such as “Interpreter in English/French”). - Produce only the translated message with no commentary. - Ask for clarification if wording is unclear, mumbled, or ambiguous. - Support until the interaction is complete. - Respect trauma-informed communication: be patient, slow down when needed, and avoid overwhelming the speaker. GUARDRAILS Do not answer questions on behalf of either party. You only interpret. Do not modify the speaker’s intent, meaning, tone, or emotional weight. Do not give legal, medical, or personal advice. Do not engage in side conversations—your role is strictly interpreting. No symbols, emojis, slang, abbreviations, or formatting that may confuse TTS. Always clarify before interpreting content that sounds incomplete. Immediately correct yourself if you realize an inaccuracy. Maintain strict confidentiality. """ ) async def entrypoint(ctx: agents.JobContext): """Entry point for the agent.""" # Configure the voice pipeline with the essentials session = AgentSession( # turn_detection="stt", # vad, stt # stt=assemblyai.STT(model='universal-streaming-multilingual'), stt=speechmatics.STT( language="en", # domain="bilingual-en", enable_partials=False, enable_diarization=True, speaker_active_format="<{speaker_id}>{text}</{speaker_id}>", ), llm=openai.LLM(model=os.getenv("LLM_CHOICE", "gpt-4.1-mini")), tts=openai.TTS(voice="echo"), vad=silero.VAD.load(), # vad = silero.VAD.load( # activation_threshold=0.65, # min_speech_duration=0.4, # min_silence_duration=1.0, # ) ) # Start the session await session.start( agent=Assistant(), room=ctx.room, # room_options=room_io.RoomOptions( # audio_input=room_io.AudioInputOptions( # noise_cancellation=noise_cancellation.BVC(), # ), # ), room_input_options=room_io.RoomInputOptions( noise_cancellation=noise_cancellation.BVC(), ), ) # Generate initial greeting await session.generate_reply( instructions="Greet the user and ask what language they need assistance in.", allow_interruptions=False ) if name == "__main__": # Run the agent agents.cli.run_app(agents.WorkerOptions(entrypoint_fnc=entrypoint))
t
Thanks — I looked at your code. Below are concise, minimal changes you can add so the session accumulates final user and agent transcripts and writes them to a JSON file when the call ends. What I’m addressing: adding transcript collection and saving on session shutdown. Add these imports near the top:
Copy code
import json
from datetime import datetime
Inside your
entrypoint
, before
await session.start(...)
, initialize the conversation list:
Copy code
conversation_history = []
Register handlers after creating
session
(but before
await session.start(...)
):
Copy code
# collect final user STT transcripts (diarized)
@session.on("user_input_transcribed")
def on_user_transcribed(t):
    if getattr(t, "is_final", False):
        conversation_history.append({
            "role": "user",
            "text": t.transcript,
            "timestamp": getattr(t, "timestamp", datetime.utcnow().isoformat())
        })

# collect final agent replies (conversation items)
@session.on("conversation_item_added")
def on_conversation_item(item):
    if getattr(item, "role", None) == "agent" and getattr(item, "final", False):
        conversation_history.append({
            "role": "agent",
            "text": getattr(item, "text", ""),
            "timestamp": getattr(item, "timestamp", datetime.utcnow().isoformat())
        })
Add a shutdown handler to save the file (place after handlers):
Copy code
@session.on("agent_shutdown")
async def on_shutdown():
    # write JSON locally - change filename or upload flow as needed
    with open("full_transcript.json", "w", encoding="utf-8") as f:
        json.dump(conversation_history, f, ensure_ascii=False, indent=2)
Notes and optional tweaks: • You can call
await session.aclose()
somewhere to force finalization before shutdown if needed. • If your STT object uses different field names for final transcripts or timestamps, replace
is_final
,
transcript
, and
timestamp
with the correct attributes from your runtime events. • To upload instead of saving locally, replace the file write with your cloud storage API call. If you’d like, tell me: • preferred output format (JSON, plain text, CSV), and • whether you want the file uploaded to S3/GCS so I can add that snippet.