enough-dentist-75383
12/16/2025, 1:22 PMtall-airline-16756
12/16/2025, 1:22 PMtall-airline-16756
12/16/2025, 1:22 PMplayback_finished event from the audio output you can use instead of relying on log hooks. It’s fired when TTS/audio playback completes and is more reliable than the log-based signal. You can handle it like this:
from <http://livekit.agents.voice.io|livekit.agents.voice.io> import PlaybackFinishedEvent
@session.output.audio.on("playback_finished")
def _on_playback_finished(ev: PlaybackFinishedEvent):
<http://logger.info|logger.info>(f"playback finished: {ev.interrupted}")
# trigger your tts_playback_finished_event here
For extra robustness you can also set tts_playback_finished_event when you receive a track-level signal (e.g., track_unpublished for the agent audio) or an RPC from an avatar synthesis server; combining signals prevents timeout-based delays.
Would you like me to implement adding the playback_finished handler (and optionally the secondary track_unpublished hook) in your codebase? If yes, tell me which option you prefer.
Sources: How to detect when an agent has finished speaking | Bringing AI avatars to voice agentsenough-dentist-75383
12/16/2025, 1:32 PMtall-airline-16756
12/16/2025, 1:32 PMplayback_finished event is emitted when playback ends — after the audio has completed (or was interrupted), not at the start.enough-dentist-75383
12/16/2025, 1:53 PMenough-dentist-75383
12/16/2025, 1:53 PMtall-airline-16756
12/16/2025, 1:54 PMplayback_finished event is emitted after audio playback ends (or is interrupted), not at the start.
• LiveKit Agents does support OpenAI tool calling (function calling). You can define tools/functions and have the LLM invoke them; support exists in both Python and Node.js (e.g., @function_tool in Python, llm.tool in Node), and it works with Realtime models. You can also control tool usage with parameters like tool_choice (auto, required, none).
Would you like a short example for Python or Node.js showing how to define a tool and handle its invocation?
Sources: Agents (Python) | livekit/livekit_composite | DeepWiki | Tool definition and use | LiveKit docs | OpenAI and LiveKit partner to turn Advanced Voice into an API | OpenAI and LiveKit partner to turn Advanced Voice into an API | OpenAI LLM plugin guide | LiveKit Docsenough-dentist-75383
12/16/2025, 1:54 PMtall-airline-16756
12/16/2025, 1:54 PM@function_tool)
from livekit.agents import Agent, function_tool
agent = Agent(...)
@function_tool(name="lookup_order", description="Find order status by ID")
def lookup_order(order_id: str) -> dict:
# your implementation: query DB or API
return {"order_id": order_id, "status": "shipped"}
# Agent will map incoming model tool calls to this function automatically.
# When running with a RealtimeModel, the model can call "lookup_order" and
# the framework will invoke the function and return results to the model.
Node.js (using llm.tool)
const { Agent } = require("livekit-agents");
const agent = new Agent(...);
agent.llm.tool("lookup_order", {
description: "Find order status by ID",
params: {
type: "object",
properties: { order_id: { type: "string" } },
required: ["order_id"],
},
}, async (args) => {
// args.order_id -> query DB or API
return { order_id: args.order_id, status: "shipped" };
});
// When the model emits a tool call for "lookup_order", the handler above runs
// and the tool output is provided back to the model/agent.
If you want, I can:
• Provide a full Realtime session example wiring a RealtimeModel to these tools.
• Show how to control tool_choice (auto, required, none) or handle complex parameter schemas.
Which would you prefer?enough-dentist-75383
12/16/2025, 1:54 PMtall-airline-16756
12/16/2025, 1:55 PM@function_tool
# example_python_realtime_tools.py
from livekit.agents import Agent, RealtimeModel, function_tool, SessionConfig
# 1) Define agent and tools
agent = Agent(name="orders-agent")
@function_tool(name="lookup_order", description="Find order status by ID")
def lookup_order(order_id: str) -> dict:
# replace with your DB/API call
return {"order_id": order_id, "status": "shipped", "eta": "2025-12-20"}
@function_tool(
name="cancel_order",
description="Cancel an order by ID",
)
def cancel_order(order_id: str, reason: str = "") -> dict:
# replace with cancellation logic
return {"order_id": order_id, "canceled": True, "reason": reason}
# 2) Configure RealtimeModel (OpenAI) — choose model and tool usage policy
realtime_model = RealtimeModel(
model="gpt-4o-realtime-preview", # example realtime model
tool_choice="auto", # allow model to call tools when useful
)
# 3) Start a realtime session (SessionConfig holds session params like token, etc.)
config = SessionConfig(session_name="orders-realtime")
session = agent.start_realtime_session(model=realtime_model, config=config)
# 4) Handle session lifecycle and messages (optional hooks)
@session.on("session_started")
def on_started(ev):
print("Realtime session started")
@session.on("tool_called")
def on_tool_called(ev):
# optional: inspect tool calls as they happen
print("Tool called:", ev.tool_name, ev.args)
# Keep running while session operates (depends on your app loop)
# session.run_forever() # pseudo-call — integrate with your app loop/async runtime
Node.js — Realtime session with llm.tool
// example_node_realtime_tools.js
const { Agent, RealtimeModel } = require("livekit-agents");
// 1) Create agent
const agent = new Agent({ name: "orders-agent-node" });
// 2) Define tools exposed to the LLM
agent.llm.tool(
"lookup_order",
{
description: "Find order status by ID",
params: {
type: "object",
properties: {
order_id: { type: "string" }
},
required: ["order_id"]
}
},
async (args) => {
// replace with DB/API call
return { order_id: args.order_id, status: "shipped", eta: "2025-12-20" };
}
);
agent.llm.tool(
"cancel_order",
{
description: "Cancel an order by ID",
params: {
type: "object",
properties: {
order_id: { type: "string" },
reason: { type: "string" }
},
required: ["order_id"]
}
},
async (args) => {
// replace with cancellation logic
return { order_id: args.order_id, canceled: true, reason: args.reason || "" };
}
);
// 3) Configure RealtimeModel and start session
const realtimeModel = new RealtimeModel({
model: "gpt-4o-realtime-preview",
tool_choice: "auto" // allow model to call tools when useful
});
(async () => {
const session = await agent.startRealtimeSession({ model: realtimeModel, sessionName: "orders-realtime" });
session.on("session_started", () => {
console.log("Realtime session started (Node)");
});
session.on("tool_called", (ev) => {
console.log("Tool called:", ev.toolName, ev.args);
});
// keep your process running or integrate with your application loop
})();
Notes and next steps I can help with:
• Show how tool outputs are returned to the model and how to surface them to users.
• Demonstrate tool_choice behaviors: auto, required, none.
• Add a frontend example showing RPC or websocket messages to react when a tool result arrives.
• Add authentication/session token wiring for your environment.
Which follow-up would you like?