```class FunctionToolsExecutedEvent(BaseModel): ...
# ask-ai
n
Copy code
class FunctionToolsExecutedEvent(BaseModel):
    type: Literal["function_tools_executed"] = "function_tools_executed"
    function_calls: list[FunctionCall]
    function_call_outputs: list[FunctionCallOutput | None]
    created_at: float = Field(default_factory=time.time)
    _reply_required: bool = PrivateAttr(default=False)
    _handoff_required: bool = PrivateAttr(default=False)

    def zipped(self) -> list[tuple[FunctionCall, FunctionCallOutput | None]]:
        return list(zip(self.function_calls, self.function_call_outputs))

    def cancel_tool_reply(self) -> None:
        self._reply_required = False

    def cancel_agent_handoff(self) -> None:
        self._handoff_required = False

    @property
    def has_tool_reply(self) -> bool:
        return self._reply_required

    @property
    def has_agent_handoff(self) -> bool:
        return self._handoff_required

    @model_validator(mode="after")
    def verify_lists_length(self) -> Self:
        if len(self.function_calls) != len(self.function_call_outputs):
            raise ValueError("The number of function_calls and function_call_outputs must match.")

        return self
can you explain what is cancel_tool_reply and _handoff_required and how it works ?
t
Thanks for reaching out, Darshak. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I’ll explain the two pieces you asked about and how they’re used in this class. Acknowledging what you asked about • You asked about
cancel_tool_reply
and the
_handoff_required
attribute on
FunctionToolsExecutedEvent
. What
cancel_tool_reply
does •
cancel_tool_reply()
sets the private flag
_reply_required
to False. • That flag is exposed by the
has_tool_reply
property. If
_reply_required
is False,
has_tool_reply
will return False. • Effect: calling
cancel_tool_reply()
tells the surrounding workflow that no LLM-generated reply is required after the tools run (useful when tool side-effects are sufficient and you don’t want a follow-up message). What
_handoff_required
is and how it works •
_handoff_required
is a private boolean flag that indicates whether an agent handoff should occur after tool execution. • The
has_agent_handoff
property exposes that flag. If
_handoff_required
is True,
has_agent_handoff
returns True. •
cancel_agent_handoff()
sets
_handoff_required
to False, cancelling a pending handoff. • Effect: when
_handoff_required
is True the framework can perform a handoff (transfer control to another agent); setting it False prevents that transfer. How these flags are used together • After tools run, the workflow checks
has_tool_reply
and
has_agent_handoff
to decide next steps: ◦ If
has_agent_handoff
is True → perform handoff. ◦ Else if
has_tool_reply
is True → generate an LLM reply from tool outputs. ◦ If both are False → no automatic reply or handoff; wait for further input or other logic. If you want, tell me whether you’re seeing unexpected behavior (e.g., a reply still being generated or a handoff occurring) and I’ll help you trace where those flags are being set. Sources: Tool definition and use | LiveKit docs
n
even if error occurred during tool execution ?
t
Yes — those flags still apply when a tool errors. • An error during tool execution does not automatically change
_reply_required
or
_handoff_required
. They remain whatever the code set them to during or after the tool run. • Typical behavior: ◦ If
_handoff_required
is True, the framework will proceed with the handoff even if a tool errored (unless
cancel_agent_handoff()
is called afterward). ◦ If
_reply_required
is True, the framework will attempt to generate an LLM reply that can include the error details (unless
cancel_tool_reply()
is called). ◦ If both flags are False, no automatic reply or handoff occurs regardless of the error; the system will wait for further input or other logic. If you want to change behavior on error, you can: • Clear or set these flags in your tool error handling (call
cancel_tool_reply()
or set
_handoff_required
as appropriate). • Add conditional logic after tool execution to inspect errors and decide whether to request a reply or perform a handoff. Do you want an example of where to set/clear the flags in error handling code?
n
I am using MCP tool calling so can i set tool specific timeout like this tool should only required x time and some other tool required only y time ?
t
Short answer: LiveKit does not provide a built‑in, declarative per‑tool timeout for MCP tool calls. You can enforce per‑tool timeouts yourself inside each tool implementation. Acknowledging your question • You asked whether you can set different time limits per tool when using MCP tool calling. What’s available • There’s no native MCP feature to declare different timeouts per tool. • The framework does support overall controls like
max_tool_steps
, and tools can be interrupted, but per‑tool timeout config is not provided. What to do instead (two common approaches) • Python (async): wrap the tool call with
asyncio.wait_for()
to enforce a timeout for that specific tool. • Node.js (async): use
Promise.race()
with a timeout promise to enforce a per‑tool timeout. Quick examples • Python:
Copy code
import asyncio

async def run_tool_with_timeout(tool_coro, timeout_seconds):
    return await asyncio.wait_for(tool_coro, timeout_seconds)
• Node.js:
Copy code
function runToolWithTimeout(toolPromise, ms) {
  const timeout = new Promise((_, rej) => setTimeout(() => rej(new Error('timeout')), ms));
  return Promise.race([toolPromise, timeout]);
}
Follow-up question • Are you using the Python or Node.js SDK, and do you want the timeout enforced on the agent side or the MCP server side? I can provide a concrete code example integrated into your tool-calling flow. Sources: Tool definition and use | LiveKit docs | Using MCP with LiveKit Agents | Agent session | LiveKit docs | Agent speech and audio | LiveKit docs