Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 6 additions & 34 deletions examples/voice_agents/session_close_callback.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,7 @@

from dotenv import load_dotenv

from livekit.agents import (
Agent,
AgentServer,
AgentSession,
CloseEvent,
JobContext,
cli,
room_io,
utils,
)
from livekit.agents import Agent, AgentServer, AgentSession, CloseEvent, JobContext, cli
from livekit.agents.beta.tools import EndCallTool
from livekit.plugins import silero

Expand All @@ -25,26 +16,16 @@
# or when the worker is shutting down. When closing the session, agent will be interrupted
# and the last agent message will be added to the chat context.

server = AgentServer()


class MyAgent(Agent):
def __init__(self):
super().__init__(
instructions="You are a helpful assistant.",
tools=[EndCallTool()],
tools=[EndCallTool(on_end="thanks the user for calling and tell them goodbye")],
)

@utils.log_exceptions(logger=logger)
async def on_exit(self) -> None:
logger.info("exiting the agent")
if self.session.current_speech:
await self.session.current_speech

logger.info("generating goodbye message")
await self.session.generate_reply(
instructions="say goodbye to the user", tool_choice="none"
)
async def on_enter(self) -> None:
self.session.generate_reply(instructions="say hello to the user")


server = AgentServer()
Expand All @@ -55,20 +36,11 @@ async def entrypoint(ctx: JobContext):
session = AgentSession(
stt="assemblyai/universal-streaming",
llm="openai/gpt-4.1-mini",
tts="rime/arcana",
tts="cartesia/sonic-3",
vad=silero.VAD.load(),
)

# session will be closed automatically when the linked participant disconnects
# with reason CLIENT_INITIATED, ROOM_DELETED, or USER_REJECTED
# or you can disable it by setting the RoomInputOptions.close_on_disconnect to False
await session.start(
agent=MyAgent(),
room=ctx.room,
room_options=room_io.RoomOptions(
delete_room_on_close=True,
),
)
await session.start(agent=MyAgent(), room=ctx.room)

@session.on("close")
def on_close(ev: CloseEvent):
Expand Down
87 changes: 71 additions & 16 deletions livekit-agents/livekit/agents/beta/tools/end_call.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,83 @@
from collections.abc import Awaitable
from typing import Callable

from ...job import get_job_context
from ...llm import Tool, Toolset, function_tool
from ...log import logger
from ...voice.events import RunContext
from ...voice.events import CloseEvent, RunContext

END_CALL_DESCRIPTION = """
Ends the current call and disconnects immediately.

Call when:
- The user clearly indicates they are done (e.g., “that’s all, bye”).
- The agent determines the conversation is complete and should end.

Do not call when:
- The user asks to pause, hold, or transfer.
- Intent is unclear.

This is the final action the agent can take.
Once called, no further interaction is possible with the user.
Don't generate any other text or response when the tool is called.
"""


class EndCallTool(Toolset):
@function_tool(name="end_call")
async def _end_call(self, ctx: RunContext) -> None:
def __init__(
self,
*,
extra_description: str = "",
delete_room: bool = True,
on_end: str | Callable[[RunContext], Awaitable[None]] | None = "say goodbye to the user",
):
"""
This tool allows the agent to end the call and disconnect from the room.

Args:
extra_description: Additional description to add to the end call tool.
delete_room: Whether to delete the room when the user ends the call. deleting the room disconnects all remote users, including SIP callers.
on_end: If a string is provided, it will be used as the instructions of
`session.generate_reply` when the user ends the call. If a callback, it will be called
when the user ends the call.
"""
Ends the current call and disconnects immediately.
super().__init__()
self._delete_room = delete_room
self._extra_description = extra_description
self._on_end = on_end

self._end_call_tool = function_tool(
self._end_call,
name="end_call",
description=f"{END_CALL_DESCRIPTION}\n{extra_description}",
)

Call when:
- The user clearly indicates they are done (e.g., “that’s all, bye”).
- The agent determines the conversation is complete and should end.
async def _end_call(self, ctx: RunContext) -> None:
try:
logger.debug("end_call tool called")
ctx.session.once("close", self._on_session_close)
if isinstance(self._on_end, str):
await ctx.session.generate_reply(instructions=self._on_end, tool_choice="none")
elif callable(self._on_end):
await self._on_end(ctx)
finally:
# close the AgentSession
ctx.session.shutdown()

Do not call when:
- The user asks to pause, hold, or transfer.
- Intent is unclear.
def _on_session_close(self, ev: CloseEvent) -> None:
job_ctx = get_job_context()

This is the final action the agent can take.
Once called, no further interaction is possible with the user.
"""
logger.debug("end_call tool called")
ctx.session.shutdown()
if self._delete_room:

async def _on_shutdown() -> None:
logger.info("deleting the room because the user ended the call")
await job_ctx.delete_room()

job_ctx.add_shutdown_callback(_on_shutdown)

# shutdown the job process
job_ctx.shutdown(reason=ev.reason.value)

@property
def tools(self) -> list[Tool]:
return [self._end_call]
return [self._end_call_tool]