rhythmic-jordan-68259
09/15/2025, 11:28 PMtall-airline-16756
09/15/2025, 11:28 PMtall-airline-16756
09/15/2025, 11:28 PMvoice.Agent class lets you configure the agent with instructions and tools seamlessly.
3. Deploy the Agent: Finally, deploy your agent using the LiveKit CLI. Run the command lk agent create [working-dir] from your project directory. Don’t forget to have your environment set up with the necessary API keys, like LIVEKIT_API_KEY and LIVEKIT_API_SECRET.
The JSON schema plays a key role in guiding the language model on what input the tool expects, so the agent can make function calls with the right arguments. If you’d like, I’m here to help you with any of these steps—just let me know!
Sources: README.md | livekit/livekit_cloud_agent.pb.go | Agents Overview | LiveKit Docsrhythmic-jordan-68259
09/15/2025, 11:29 PMtall-airline-16756
09/15/2025, 11:29 PMimport { llm, voice } from '@livekit/agents';
import { z } from 'zod';
// Define the tool using JSON schema
const weatherTool = llm.tool({
description: 'Get the weather for a specific location.',
parameters: z.object({
location: z.string().describe('The city and state, e.g., New York, US'),
units: z.enum(['metric', 'imperial']).optional().describe('Units for temperature (metric or imperial)'),
}),
execute: async (args) => {
const { location, units } = args;
const response = await fetch(`${process.env.WEATHER_API_URL}?q=${location}&units=${units || 'metric'}&appid=${process.env.WEATHER_API_KEY}`);
const data = await response.json();
return { temperature: data.list[0].main.temp, description: data.list[0].weather[0].description };
},
});
// Create the agent
const agent = new voice.Agent({
instructions: 'You are a weather forecast assistant. Greet the user and provide weather updates.',
tools: { weatherTool },
});
// Deploy the agent (use LiveKit CLI for deployment)
// Ensure you have set up LIVEKIT_API_KEY and LIVEKIT_API_SECRET in your environment
When you're ready to deploy this agent, just save the code in your project directory and run the LiveKit CLI command:
lk agent create [working-dir]
Just a quick note: make sure to replace process.env.WEATHER_API_URL and process.env.WEATHER_API_KEY with your actual API URL and key in your environment variables.
If you have any questions or need a hand with the deployment, please don’t hesitate to reach out. I’m here to help!rhythmic-jordan-68259
09/15/2025, 11:30 PMtall-airline-16756
09/15/2025, 11:30 PMfrom livekit.agents import Agent, AgentSession, JobContext, WorkerOptions, cli
from livekit.plugins import deepgram, openai, silero
# Define the entrypoint function
async def entrypoint(ctx: JobContext):
await ctx.connect()
session = AgentSession(
vad=silero.VAD.load(),
stt=deepgram.STT(model="nova-3"),
llm=openai.LLM(model="gpt-4o-mini"),
tts=elevenlabs.TTS(),
)
await session.start(agent=Agent(instructions="You are a friendly voice assistant."), room=ctx.room)
await session.generate_reply(instructions="Greet the user and ask about their day.")
# Run the agent
if __name__ == "__main__":
cli.run_app(WorkerOptions(entrypoint_fnc=entrypoint))
Here are some simple steps to follow:
1. Install Dependencies: Just run pip install "livekit-agents[openai,silero,deepgram,cartesia,turn-detector]~=1.0". This will get everything you need set up.
2. Set Environment Variables: Make sure to add your API keys like DEEPGRAM_API_KEY, OPENAI_API_KEY, and ELEVEN_API_KEY into a .env file or directly into your environment. This helps the agent connect smoothly.
3. Run the Agent: To see it in action, start the agent in development mode with python3 agent.py dev.
If you have any questions or want me to help tweak this example, just let me know. I'm here to help!
Sources: Accepting incoming calls | LiveKit Docs | Accepting incoming calls | LiveKit Docs | livekit-agents/README.md | Agents Overview | LiveKit Docs | Agents Overview | LiveKit Docsrhythmic-jordan-68259
09/15/2025, 11:31 PMtall-airline-16756
09/15/2025, 11:32 PMasync def llm_node(self, chat_ctx, tools, model_settings):
tool_call_detected = False
async for chunk in super().llm_node(chat_ctx, tools, model_settings):
if isinstance(chunk, ChatChunk) and chunk.delta.tool_calls and not tool_call_detected:
self._activity.agent.say("Checking that for you...")
tool_call_detected = True
yield chunk
2. Tools
Tools are what let your agent connect with external systems. You can define them easily using the @function_tool decorator or even add them dynamically. Here's a simple way to do it:
@function_tool()
async def get_weather(location: str) -> str:
"""Get the weather in a specific location."""
return f"The weather in {location} is sunny."
agent = MyAgent(
instructions="You are a helpful assistant.",
tools=[get_weather]
)
3. Edges
Regarding edges from your JSON schema, the Python SDK handles the flow between nodes (like STT → LLM → TTS) behind the scenes. While edges aren't explicitly defined, you can guide this flow by:
• Overriding nodes such as llm_node
• Using lifecycle hooks like on_user_turn_completed()
• Dynamically updating tools with agent.update_tools()
4. Example Implementation
Here’s a warm and practical example of defining a tool and weaving it into your agent:
from livekit.agents import Agent, function_tool
@function_tool()
async def get_weather(location: str) -> dict:
"""Fetch weather data for a location."""
# Replace with actual API call logic
return {"temperature": 25, "description": "Sunny"}
class WeatherAgent(Agent):
async def llm_node(self, chat_ctx, tools, model_settings):
# Custom logic for LLM node
async for chunk in super().llm_node(chat_ctx, tools, model_settings):
yield chunk
agent = WeatherAgent(
instructions="You are a weather assistant.",
tools=[get_weather]
)
If you’d like, I’m here to help with any further questions or to provide a more tailored example that fits your JSON schema perfectly!
Sources: Tool definition and use | LiveKit Docs | Pipeline nodes and hooks | LiveKit Docs | mcp_client/agent_tools.py | Docs::Agents | Tool definition and use | LiveKit Docsrhythmic-jordan-68259
09/15/2025, 11:37 PMtall-airline-16756
09/15/2025, 11:37 PMrhythmic-jordan-68259
09/15/2025, 11:56 PMimport asyncio
import logging
import aiohttp
from livekit.agents import JobContext, WorkerOptions, cli, function_tool, Agent, AgentSession
from livekit.plugins import openai, silero
from livekit.agents.llm import ChatChunk
from dotenv import load_dotenv
load_dotenv()
logger = logging.getLogger("weather-agent")
# Configuration from your schema
WEATHER_API_KEY = "a4c3da3f5a7c97acba02069a3401d0f3"
WEATHER_API_URL = "<https://api.openweathermap.org/data/2.5/forecast>"
# Global state to track conversation flow
conversation_state = {
"current_node": "welcome",
"temperature": None,
"location": None
}
class WeatherAgent(Agent):
"""Weather Agent following the conversation flow schema"""
def __init__(self) -> None:
# Build instructions based on conversation flow schema
instructions = """You are a weather forecast assistant following a specific conversation flow.
CONVERSATION FLOW:
1. Welcome Node: Greet user and help them get weather updates
2. Weather Node: When user asks about weather, use get_weather tool
3. Conversation Node: After getting weather, explain the temperature clearly
4. End Node: When user wants to end, use end_call tool
CURRENT BEHAVIOR:
- Start by greeting the user warmly
- When they ask about weather, use the get_weather(location) tool
- After getting weather data, explain the current temperature clearly
- Ask if they need anything else
- When they want to end, use the end_call() tool
Be natural and conversational while following this flow."""
super().__init__(
instructions=instructions,
tools=[self.get_weather, self.end_call]
)
@function_tool()
async def get_weather(self, location: str = "New York,US") -> dict:
"""Get the weather in a specific location - corresponds to Weather tool from schema"""
<http://logger.info|logger.info>(f"Getting weather for: {location}")
# Update conversation state
conversation_state["current_node"] = "weather_function"
conversation_state["location"] = location
try:
# Use exact parameters from your schema
params = {
"q": location,
"units": "metric",
"appid": WEATHER_API_KEY
}
async with aiohttp.ClientSession() as session:
async with session.get(WEATHER_API_URL, params=params) as response:
if response.status == 200:
data = await response.json()
# Extract temperature using schema path: list.main.temp
if data.get("list") and len(data["list"]) > 0:
temperature = data["list"][0]["main"]["temp"]
description = data["list"][0]["weather"][0]["description"]
# Store temperature for template replacement
conversation_state["temperature"] = temperature
conversation_state["current_node"] = "conversation"
return {
"temperature": temperature,
"description": description,
"location": location
}
else:
return {"error": "No weather data available"}
else:
return {"error": f"API request failed"}
except Exception as e:
logger.error(f"Weather API error: {e}")
return {"error": str(e)}
@function_tool()
async def end_call(self,) -> str:
"""Politely end the call - corresponds to End Call node"""
<http://logger.info|logger.info>("Ending conversation")
conversation_state["current_node"] = "end"
return "Thank you for using the weather service! Have a wonderful day!"
async def llm_node(self, chat_ctx, tools, model_settings):
"""Custom logic for LLM node with flow-based feedback"""
tool_call_detected = False
async for chunk in super().llm_node(chat_ctx, tools, model_settings):
# Provide feedback when tools are called
if isinstance(chunk, ChatChunk) and chunk.delta.tool_calls and not tool_call_detected:
current_node = conversation_state["current_node"]
if current_node == "welcome":
# Note: We'll handle feedback through the session
pass
elif current_node == "weather_function":
# Note: We'll handle feedback through the session
pass
tool_call_detected = True
yield chunk
async def entrypoint(ctx: JobContext):
"""Main entry point for the weather agent"""
<http://logger.info|logger.info>("Starting Weather Forecast Agent")
# Reset conversation state
conversation_state["current_node"] = "welcome"
conversation_state["temperature"] = None
conversation_state["location"] = None
# Create agent session
session = AgentSession(
stt=openai.STT(),
llm=openai.LLM(model="gpt-4", temperature=0.7),
tts=openai.TTS(),
vad=silero.VAD.load(),
)
# Start the session with our weather agent
await session.start(
room=ctx.room,
agent=WeatherAgent(),
)
# Send initial greeting (Welcome Node behavior)
await session.generate_reply(
instructions=(
"Hello! I'm your weather forecast assistant. "
"I can help you get current weather information for any location. "
"What location would you like to check the weather for?"
)
)
<http://logger.info|logger.info>("Weather agent started and ready")
if __name__ == "__main__":
# Set up logging
logging.basicConfig(
level=<http://logging.INFO|logging.INFO>,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# Run the agent
cli.run_app(
WorkerOptions(
entrypoint_fnc=entrypoint,
),
)
Error
2025-09-16 045508,747 - DEBUG livekit.agents - http_session(): creating a new httpclient ctx
2025-09-16 045508,778 - asyncio - ERROR - Task exception was never retrieved
future: <Task finished name='AgentActivity.pipeline_reply' coro=<AgentActivity._pipeline_reply_task() done, defined at /home/umair/.cache/pypoetry/virtualenvs/livekit-workflow-TFmTJtXu-py3.12/lib/python3.12/site-packages/opentelemetry/util/_decorator.py:68> exception=ValueError('duplicate function name: end_call')>
Traceback (most recent call last):
File "/home/umair/.cache/pypoetry/virtualenvs/livekit-workflow-TFmTJtXu-py3.12/lib/python3.12/site-packages/opentelemetry/util/_decorator.py", line 71, in async_wrapper
return await func(*args, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/umair/.cache/pypoetry/virtualenvs/livekit-workflow-TFmTJtXu-py3.12/lib/python3.12/site-packages/livekit/agents/utils/log.py", line 16, in async_fn_logs
return await fn(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/umair/.cache/pypoetry/virtualenvs/livekit-workflow-TFmTJtXu-py3.12/lib/python3.12/site-packages/livekit/agents/voice/agent_activity.py", line 1544, in _pipeline_reply_task
tool_ctx = llm.ToolContext(tools)
^^^^^^^^^^^^^^^^^^^^^^
File "/home/umair/.cache/pypoetry/virtualenvs/livekit-workflow-TFmTJtXu-py3.12/lib/python3.12/site-packages/livekit/agents/llm/tool_context.py", line 219, in init
self.update_tools(tools)
File "/home/umair/.cache/pypoetry/virtualenvs/livekit-workflow-TFmTJtXu-py3.12/lib/python3.12/site-packages/livekit/agents/llm/tool_context.py", line 247, in update_tools
raise ValueError(f"duplicate function name: {info.name}")
ValueError: duplicate function name: end_call
2025-09-16 045508,778 - ERROR asyncio - Task exception was never retrieved
future: <Task finished name='AgentActivity.pipeline_reply' coro=<AgentActivity._pipeline_reply_task() done, defined at /home/umair/.cache/pypoetry/virtualenvs/livekit-workflow-TFmTJtXu-py3.12/lib/python3.12/site-packages/opentelemetry/util/_decorator.py:68> exception=ValueError('duplicate function name: end_call')>
Traceback (most recent call last):
File "/home/umair/.cache/pypoetry/virtualenvs/livekit-workflow-TFmTJtXu-py3.12/lib/python3.12/site-packages/opentelemetry/util/_decorator.py", line 71, in async_wrapper
return await func(*args, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/umair/.cache/pypoetry/virtualenvs/livekit-workflow-TFmTJtXu-py3.12/lib/python3.12/site-packages/livekit/agents/utils/log.py", line 16, in async_fn_logs
return await fn(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/umair/.cache/pypoetry/virtualenvs/livekit-workflow-TFmTJtXu-py3.12/lib/python3.12/site-packages/livekit/agents/voice/agent_activity.py", line 1544, in _pipeline_reply_task
tool_ctx = llm.ToolContext(tools)
^^^^^^^^^^^^^^^^^^^^^^
File "/home/umair/.cache/pypoetry/virtualenvs/livekit-workflow-TFmTJtXu-py3.12/lib/python3.12/site-packages/livekit/agents/llm/tool_context.py", line 219, in init
self.update_tools(tools)
File "/home/umair/.cache/pypoetry/virtualenvs/livekit-workflow-TFmTJtXu-py3.12/lib/python3.12/site-packages/livekit/agents/llm/tool_context.py", line 247, in update_tools
raise ValueError(f"duplicate function name: {info.name}")
ValueError: duplicate function name: end_callrhythmic-jordan-68259
09/16/2025, 12:01 AM