In a multi agents system, How to make sub agent wo...
# ask-ai
c
In a multi agents system, How to make sub agent work as a tool, it receive request from parent agent and retur result to parent agent
t
Thanks for reaching out, wenwen. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — you're asking how a sub‑agent can act as a tool: receive a request from a parent agent and return the result back. I’ll summarize the two practical patterns you can use and when to pick each, plus a short example you can adapt. Which pattern do you want to use: a lightweight function-style tool (direct return) or a full agent handoff (sub-agent takes control, then returns)? If you tell me which, I’ll give a minimal code example tailored to your stack. Quick overview • Function tool (direct return) ◦ Use when the sub-agent’s work is simple/stateless and returns a value immediately. ◦ Implement as a @function_tool that executes logic and returns the result string/object directly to the parent. ◦ Parent LLM receives the result and continues the conversation. • Agent handoff (sub-agent as conversational tool) ◦ Use when the sub-agent needs full conversational control, longer processing, or its own messaging flow. ◦ Parent’s tool returns an instance of the sub-agent (hand off control). The sub-agent runs (e.g., in on_enter), performs work, then hands control back to the parent with a result message or by returning the parent agent instance plus a result string. ◦ State and context are exchanged via chat context (
chat_ctx
) or metadata (
job.metadata
,
room.metadata
, participant attributes). Minimal examples • Function tool (direct return)
Copy code
@function_tool()
async def do_simple_task(self, context: RunContext, input: str):
    # perform quick computation or external call
    return f"Result for {input}"
• Agent handoff
Copy code
class ParentAgent(Agent):
    @function_tool()
    async def call_specialist(self, context: RunContext, data: str):
        return SpecialistAgent(), "Handing off to specialist"

class SpecialistAgent(Agent):
    async def on_enter(self) -> None:
        # do work
        result = "specialist result"
        # hand back to parent with result message
        return ParentAgent(), f"Returning result: {result}"
Passing data and best practices • Pass inputs and results via
chat_ctx
messages or metadata fields so both agents can access conversation state. • Keep agents stateless where possible; store persistent state in metadata. • Use function tool for fast/synchronous needs; use handoff for complex or multi-turn processing. • If you need, I can convert one of the examples into a fuller implementation for your language/runtime or show how to store/read results in
job.metadata
or
room.metadata
. Which pattern should I expand into a full example for you? Sources: Agents and handoffs | LiveKit docs | External data and RAG | LiveKit docs | Tool definition and use | LiveKit docs | Building Multi-Agent Architectures with LiveKit Agents | LiveKit Docs
c
function call
t
Great — since you want the function-call pattern, here’s a concise, practical guide and a minimal code example you can adapt. When to use this: • Use a function tool when the sub-agent’s work is simple, stateless, and can synchronously return a result to the parent agent. How it works: • Define the sub-agent logic as a
@function_tool()
on the parent agent (or as a callable helper the tool invokes). • The tool executes the logic and returns the result directly to the parent LLM flow. • Pass any needed context via the tool arguments or `chat_ctx`/metadata. Minimal example (Python-style)
Copy code
from livekit.agents import Agent, function_tool, RunContext

class ParentAgent(Agent):
    def __init__(self):
        super().__init__(instructions="You are the parent agent.")

    @function_tool()
    async def call_specialist(self, context: RunContext, task_input: str):
        # Option A: simple inline logic
        result = f"Processed inline: {task_input[::-1]}"  # example operation
        return result

        # Option B: delegate to a helper/sub-module (still a direct return)
        # result = await specialist_helper.process(task_input)
        # return result

# Example helper (if you want separation but still a direct return)
class SpecialistHelper:
    @staticmethod
    async def process(data: str) -> str:
        # external API call, computation, DB lookup, etc.
        return f"Specialist result for {data}"
Best practices • Keep tools idempotent and stateless; store long-term state in metadata if needed. • Validate inputs in the tool and return structured results (JSON/string) that the parent agent can parse. • Use
chat_ctx
or metadata only when you need shared context beyond function arguments. • For longer-running or multi-turn work, use the agent-handoff pattern instead. Would you like this adapted to a specific runtime (TypeScript/Node, Python async framework) or an example showing structured JSON returns and parsing?
c
your tool does not use sub agent
t
Thanks — I see you expect the tool to actually invoke a sub‑agent. You’re right: a plain function tool can be just inline logic. If you want a function-call style tool that creates or calls a sub‑agent and returns that agent’s result synchronously to the parent, you can implement the tool to instantiate/run the sub‑agent and return its final result. Do you prefer a Python or TypeScript example? I’ll give a minimal Python async example below that shows a
@function_tool
calling a sub‑agent instance, awaiting its work, and returning the sub‑agent’s result back to the parent. Python example
Copy code
from livekit.agents import Agent, function_tool, RunContext

class SpecialistAgent(Agent):
    def __init__(self, payload: str):
        super().__init__(instructions="Specialist agent")
        self.payload = payload
        self.result = None

    async def run_task(self):
        # perform specialist work (sync/async external calls allowed)
        self.result = f"specialist processed: {self.payload[::-1]}"
        return self.result

class ParentAgent(Agent):
    @function_tool()
    async def call_specialist(self, context: RunContext, task_input: str):
        # instantiate sub-agent
        specialist = SpecialistAgent(task_input)
        # run its work directly and get result
        result = await specialist.run_task()
        # return result directly to parent LLM flow
        return result
Notes • This keeps the tool-call pattern (parent calls a function tool) while actually using a sub‑agent object to encapsulate logic. • For multi‑turn conversational handoffs (sub‑agent needs conversational control), use the agent-handoff pattern instead (return the sub‑agent instance from the tool and let it hand back control when done). • If you want TypeScript/Node or an example that integrates chat_ctx or metadata storage, tell me which and I’ll expand it. Which language/runtime should I produce next?
c
sub agent need to communicate llm
t
Acknowledging: you want a function-call tool that invokes a sub‑agent which itself talks to an LLM and returns the sub‑agent’s result back to the parent. Below is a concise Python pattern you can adapt. It shows a parent tool that starts a sub‑agent, the sub‑agent uses its own LLM to generate a reply, and the parent receives the final result synchronously.
Copy code
from livekit.agents import Agent, function_tool, AgentSession, AgentInputContext

# Sub-agent that uses its own LLM to process input and produce a result
class SubAgent(Agent):
    def __init__(self, prompt: str):
        super().__init__(instructions="You are a specialist agent.")
        self.prompt = prompt
        self.result = None

    async def run_and_return(self):
        # start a short session for the sub-agent to call its LLM and produce output
        session = AgentSession()
        await session.start(agent=self, input_ctx=AgentInputContext(text_input=self.prompt))
        await session.wait_for_completion()
        # read last message produced by the sub-agent's LLM from chat_ctx
        last_msg = self.session.chat_ctx.messages[-1] if self.session and self.session.chat_ctx.messages else None
        self.result = last_msg.content[0] if last_msg and last_msg.content else None
        return self.result

# Parent agent with a function-tool that instantiates and awaits the sub-agent
class ParentAgent(Agent):
    @function_tool()
    async def call_subagent(self, context, user_text: str):
        sub = SubAgent(user_text)
        # run the sub-agent and get its LLM-produced result
        result = await sub.run_and_return()
        # return the result directly to the parent LLM flow
        return result
Key points/practices • The sub-agent runs its own AgentSession so it can call its LLM independently. • The parent tool awaits the sub-agent’s completion and returns the sub-agent’s final content as the tool result. • Exchange inputs via the tool arguments; exchange outputs via the tool return value (or structured JSON). • Use
chat_ctx
or metadata (
job.metadata
/
room.metadata
) if you need richer shared state across agents. • For multi‑turn conversational control, prefer the handoff pattern (return agent instance), but for synchronous function-style results use the pattern above. Would you like this translated to TypeScript/Node, or an example that returns structured JSON and shows parsing in the parent? Sources: Agents and handoffs | LiveKit docs | Workflows | LiveKit docs
c
AgentSession does not have wait_for_completion function
t
Acknowledging: AgentSession has no
wait_for_completion()
— thanks for pointing that out. Here are the correct patterns to get a sub‑agent to communicate with an LLM and return results. Two supported approaches 1) Agent handoff (tool returns an agent instance) • Use when the sub‑agent needs conversational control or multi‑turn interaction. • Implement the parent tool to return `SubAgent()`; LiveKit performs a handoff and the sub‑agent runs. When it finishes (via
on_exit
or returning control), conversation context and any shared state are available to the parent. • Pass results via shared state (e.g.,
session.userdata
,
job.metadata
,
room.metadata
) or by putting the result into the chat context before exit. Minimal example (handoff)
Copy code
@function_tool()
async def call_specialist(self, ctx):
    return SpecialistAgent(), "Handing off to specialist"

class SpecialistAgent(Agent):
    async def on_enter(self):
        # call LLM, produce result, store it
        await self.session.generate_reply("Processing...")
        self.session.userdata["specialist_result"] = "result text"
        # return control (implicitly or explicitly) to parent when done
2) AgentTask (blocking, awaitable, returns typed result) • Use when you need a synchronous-like call that returns a value the parent can
await
. • Implement
AgentTask
to run LLM work and call `self.complete(result)`; then
result = await SubTask()
in the parent. Minimal example (AgentTask)
Copy code
class SummaryTask(AgentTask[dict]):
    async def on_enter(self):
        # run LLM work, then complete with a result
        summary = await self.llm.generate("Summarize X")
        self.complete({"summary": summary})

# In parent agent
result = await SummaryTask()
# result is the dict produced by the task
Why AgentSession.wait_for_completion() is wrong •
AgentSession
does not expose
wait_for_completion()
. Attempting to await it is unsupported. • Use handoffs (return agent instances) for conversational control, or
AgentTask
for awaitable, result-returning work. Passing results back • For handoffs: write result to
session.userdata
or chat messages before sub-agent exit so the parent can read it after control returns. • For tasks: return the result via
self.complete(...)
and consume it from the parent via
await
. If you’d like, I can: • Convert one of these patterns into a fuller Python or TypeScript example tailored to your runtime, or • Show exactly how to store/read
session.userdata
or put a structured JSON result into chat context for the parent to consume. Which would help you most? Sources: Agents and handoffs | LiveKit docs | Tool definition and use | LiveKit docs | Tasks and task groups | LiveKit docs