Skip to content

Commit bd5df65

Browse files
committed
modules
1 parent 7fc223b commit bd5df65

File tree

5 files changed

+347
-0
lines changed

5 files changed

+347
-0
lines changed

module-2.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Adding turn detection
2+
3+
In this exercise, we'll add a semantic turn detector model to the agent.
4+
5+
Step 1: Intall the package
6+
7+
```shell
8+
uv add "livekit-agents[turn-detector]"
9+
```
10+
11+
Step 2: Import the package
12+
13+
```python
14+
from livekit.plugins.turn_detector.multilingual import MultilingualModel
15+
```
16+
17+
Step 3: Add the model to the agent (inside the `AgentSession` constructor)
18+
19+
```python
20+
turn_detection=MultilingualModel(),
21+
```
22+
23+
Step 4: Run the agent
24+
25+
```shell
26+
uv run agent.py console
27+
```
28+
29+
Now you should be able to see the turn detection in action.

module-3.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Customizing the agent's behavior
2+
3+
Exercise 1: Change the agent's instructions and personality. Modify the system prompt in the `Assistant` class:
4+
5+
```python
6+
instructions="""
7+
You are a hilariously funny voice AI assistant.
8+
You are also a bit sarcastic.
9+
Assist the user, but don't be too helpful.
10+
""",
11+
```
12+
13+
Exercise 2: Change the agent's voice. Modify the `openai.TTS` constructor:
14+
15+
```python
16+
tts=openai.TTS(voice="ash"),
17+
```
18+
19+
Exercise 3: Add the fallback adapters.
20+
21+
Import the `stt`, `llm`, `tts` modules:
22+
23+
```python
24+
from livekit.agents import stt, llm, tts
25+
```
26+
27+
Add the fallback adapter to the `AgentSession` constructor, using OpenAI as the fallback (since it's already installed).
28+
29+
Extract the VAD from the `AgentSession` constructor:
30+
31+
```python
32+
vad = silero.VAD.load()
33+
# ...
34+
vad=vad,
35+
```
36+
37+
Add the fallback adapters to the `AgentSession` constructor:
38+
39+
```python
40+
llm=llm.FallbackAdapter(
41+
[
42+
openai.LLM(model="gpt-4.1"),
43+
openai.LLM(model="gpt-4o-mini"),
44+
]
45+
),
46+
stt=stt.FallbackAdapter(
47+
[
48+
deepgram.STT(model="nova-3", language="multi"),
49+
stt.StreamAdapter(stt=openai.STT(model="gpt-4o-transcribe"), vad=vad),
50+
]
51+
),
52+
tts=tts.FallbackAdapter(
53+
[
54+
openai.TTS(voice="ash"),
55+
deepgram.TTS(),
56+
]
57+
),
58+
```

module-4.md

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Adding metrics collection
2+
3+
In this exercise, we'll add metrics collection to the agent. This includes stats on each components, as well as a custom stat measuring the total time for the agent to respond in audio.
4+
5+
Step 1: Add the import to the `agent.py` file:
6+
7+
```python
8+
from livekit.agents import metrics, MetricsCollectedEvent, AgentStateChangedEvent
9+
```
10+
11+
Step 2: Add the metrics collection to the `entrypoint` function, before the `session.start` call:
12+
13+
```python
14+
usage_collector = metrics.UsageCollector()
15+
last_eou_metrics: metrics.EOUMetrics | None = None
16+
17+
@session.on("metrics_collected")
18+
def _on_metrics_collected(ev: MetricsCollectedEvent):
19+
nonlocal last_eou_metrics
20+
if ev.metrics.type == "eou_metrics":
21+
last_eou_metrics = ev.metrics
22+
23+
metrics.log_metrics(ev.metrics)
24+
usage_collector.collect(ev.metrics)
25+
26+
async def log_usage():
27+
summary = usage_collector.get_summary()
28+
logger.info(f"Usage: {summary}")
29+
30+
ctx.add_shutdown_callback(log_usage)
31+
32+
@session.on("agent_state_changed")
33+
def _on_agent_state_changed(ev: AgentStateChangedEvent):
34+
if (
35+
ev.new_state == "speaking"
36+
and last_eou_metrics
37+
and last_eou_metrics.speech_id == session.current_speech.id
38+
):
39+
logger.info(
40+
f"Agent response - Time to first audio frame: {ev.created_at - last_eou_metrics.last_speaking_time}"
41+
)
42+
```
43+
44+
Now you should see real merics appear in the console when you run the agent.
45+
46+
# Pre-emptive generation
47+
48+
Now we'll turn on a feature to speed up handling of long messages.
49+
50+
Add the pre-emptive generation to the `AgentSession` constructor:
51+
52+
```python
53+
preemptive_generation=True,
54+
```
55+
56+
Compare the complete response latency before and after the change.
57+
58+
59+
# Optional: Langfuse tracing
60+
61+
To add Langfuse to the agent, create an account at [Langfuse](https://langfuse.com/) and get an API key (you'll need to create an organization and a project first).
62+
63+
Step 1: Add your keys to the `.env.local` file:
64+
65+
```
66+
LANGFUSE_PUBLIC_KEY=
67+
LANGFUSE_SECRET_KEY=
68+
LANGFUSE_HOST=
69+
```
70+
71+
Step 2: Import the telemetry modules:
72+
73+
```python
74+
from livekit.agents.telemetry import set_tracer_provider
75+
import os
76+
import base64
77+
```
78+
79+
Step 3: Define the `setup_langfuse` function in the `agent.py` file:
80+
81+
```python
82+
def setup_langfuse(
83+
host: str | None = None, public_key: str | None = None, secret_key: str | None = None
84+
):
85+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
86+
from opentelemetry.sdk.trace import TracerProvider
87+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
88+
89+
public_key = public_key or os.getenv("LANGFUSE_PUBLIC_KEY")
90+
secret_key = secret_key or os.getenv("LANGFUSE_SECRET_KEY")
91+
host = host or os.getenv("LANGFUSE_HOST")
92+
93+
if not public_key or not secret_key or not host:
94+
raise ValueError("LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY, and LANGFUSE_HOST must be set")
95+
96+
langfuse_auth = base64.b64encode(f"{public_key}:{secret_key}".encode()).decode()
97+
os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"] = f"{host.rstrip('/')}/api/public/otel"
98+
os.environ["OTEL_EXPORTER_OTLP_HEADERS"] = f"Authorization=Basic {langfuse_auth}"
99+
100+
trace_provider = TracerProvider()
101+
trace_provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
102+
set_tracer_provider(trace_provider)
103+
```
104+
105+
Step 4: Add the `setup_langfuse` function call to the `entrypoint` function:
106+
107+
```python
108+
async def entrypoint(ctx: JobContext):
109+
setup_langfuse()
110+
```

module-5.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Add funcion tools
2+
3+
In this exercise, we'll add function tools to the agent.
4+
5+
Step 1: Add the import to the `agent.py` file:
6+
7+
```python
8+
from livekit.agents.llm import function_tool
9+
from livekit.agents import RunContext
10+
import aiohttp
11+
```
12+
13+
Step 2: Add a simple function tool to the `Assistant` class:
14+
15+
```python
16+
@function_tool
17+
async def lookup_weather(self, context: RunContext, location: str):
18+
"""Use this tool to look up current weather information in the given location.
19+
20+
If the location is not supported by the weather service, the tool will indicate this. You must tell the user the location's weather is unavailable.
21+
22+
Args:
23+
location: The location to look up weather information for (e.g. city name)
24+
"""
25+
26+
logger.info(f"Looking up weather for {location}")
27+
28+
try:
29+
async with aiohttp.ClientSession() as session:
30+
async with session.get(f"http://shayne.app/weather?location={location}") as response:
31+
if response.status == 200:
32+
data = await response.json()
33+
condition = data.get("condition", "unknown")
34+
temperature = data.get("temperature", "unknown")
35+
unit = data.get("unit", "degrees")
36+
return f"{condition} with a temperature of {temperature} {unit}"
37+
else:
38+
logger.error(f"Weather API returned status {response.status}")
39+
return "Weather information is currently unavailable for this location."
40+
except Exception as e:
41+
logger.error(f"Error fetching weather: {e}")
42+
return "Weather service is temporarily unavailable."
43+
```
44+
45+
# Add MCP servers
46+
47+
In this exercise, we'll add an MCP server to the agent.
48+
49+
Step 1: Install the MCP package
50+
51+
```shell
52+
uv add "livekit-agents[mcp]"
53+
```
54+
55+
Step 2: Add the import to the `agent.py` file:
56+
57+
```python
58+
from livekit.agents import mcp
59+
```
60+
61+
Step 3: Add the MCP servers to the `Assistant` class's super constructor:
62+
63+
```python
64+
mcp_servers=[
65+
mcp.MCPServerHTTP(url="https://shayne.app/sse"),
66+
],
67+
```
68+
69+
Your agent now has a simple MCP server that supports a tool called `add_numbers`.

module-6.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Collecting recording consent
2+
3+
In this exercise, we'll add a task to the agent to collect recording consent.
4+
5+
Step 1: Add the import to the `agent.py` file:
6+
7+
```python
8+
from livekit.agents import AgentTask
9+
```
10+
11+
Step 2: Define the `CollectConsent` class:
12+
13+
```python
14+
class CollectConsent(AgentTask[bool]):
15+
def __init__(self, chat_ctx=None):
16+
super().__init__(
17+
instructions="""
18+
Ask for recording consent and get a clear yes or no answer.
19+
Be polite and professional.
20+
""",
21+
chat_ctx=chat_ctx,
22+
)
23+
24+
async def on_enter(self) -> None:
25+
await self.session.generate_reply(instructions="""
26+
Briefly introduce yourself, then ask for permission to record the call for quality assurance and training purposes.
27+
Make it clear that they can decline.
28+
```
29+
30+
@function_tool
31+
async def consent_given(self) -> None:
32+
"""Use this when the user gives consent to record."""
33+
self.complete(True)
34+
35+
@function_tool
36+
async def consent_denied(self) -> None:
37+
"""Use this when the user denies consent to record."""
38+
self.complete(False)
39+
```
40+
41+
Step 3: Start the task in the `Assistant` class's `on_enter` method, then proceed based on the result:
42+
43+
```python
44+
async def on_enter(self) -> None:
45+
if await CollectConsent(chat_ctx=self.chat_ctx):
46+
logger.info("User gave consent to record.")
47+
await self.session.generate_reply(instructions="Thank the user for their consent then offer your assistance.")
48+
else:
49+
logger.info("User did not give consent to record.")
50+
await self.session.generate_reply(instructions="Let the user know that the call will not be recorded, then offer your assistance.")
51+
```
52+
53+
# Add a handoff
54+
55+
In this exercise, we'll add a handoff to the agent.
56+
57+
Step 1: Create another agent to handoff to:
58+
59+
```python
60+
class Manager(Agent):
61+
def __init__(self, chat_ctx=None):
62+
super().__init__(
63+
instructions="""You are a manager for a team of helpful voice AI assistants.
64+
A customer has been escalated to you.
65+
Provide your assistant and be professional.
66+
""",
67+
tts=openai.TTS(voice="coral"),
68+
chat_ctx=chat_ctx,
69+
)
70+
```
71+
72+
Step 2: Add the handoff to the `Assistant` class, as a function tool:
73+
74+
```python
75+
@function_tool
76+
async def escalate_to_manager(self, context: RunContext):
77+
"""Use this tool to escalate the call to the manager, upon user request."""
78+
return Manager(chat_ctx=self.chat_ctx), "Escalating to manager..."
79+
```
80+
81+
Now you can ask the assistant to escalate to the manager.

0 commit comments

Comments
 (0)