-
Notifications
You must be signed in to change notification settings - Fork 636
/
Copy pathfunction_calling_weather.py
138 lines (120 loc) Β· 5.2 KB
/
function_calling_weather.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import logging
import random
from typing import Annotated
import aiohttp
from dotenv import load_dotenv
from livekit.agents import (
AutoSubscribe,
JobContext,
JobProcess,
WorkerOptions,
cli,
llm,
)
from livekit.agents.pipeline import AgentCallContext, VoicePipelineAgent
from livekit.plugins import deepgram, openai, silero
load_dotenv()
logger = logging.getLogger("weather-demo")
logger.setLevel(logging.INFO)
class AssistantFnc(llm.FunctionContext):
"""
The class defines a set of LLM functions that the assistant can execute.
"""
@llm.ai_callable()
async def get_weather(
self,
location: Annotated[
str, llm.TypeInfo(description="The location to get the weather for")
],
latitude: Annotated[
str,
llm.TypeInfo(description="The latitude of location to get the weather for"),
],
longitude: Annotated[
str,
llm.TypeInfo(
description="The longitude of location to get the weather for"
),
],
):
"""Called when the user asks about the weather. This function will return the weather for the given location.
When given a location, please estimate the latitude and longitude of the location and do not ask the user for them."""
# When a function call is running, there are a couple of options to inform the user
# that it might take awhile:
# Option 1: you can use .say filler message immediately after the call is triggered
# Option 2: you can prompt the agent to return a text response when it's making a function call
agent = AgentCallContext.get_current().agent
if (
not agent.chat_ctx.messages
or agent.chat_ctx.messages[-1].role != "assistant"
):
# skip if assistant already said something
filler_messages = [
"Let me check the weather in {location} for you.",
"Let me see what the weather is like in {location} right now.",
# LLM will complete this sentence if it is added to the end of the chat context
"The current weather in {location} is ",
]
message = random.choice(filler_messages).format(location=location)
logger.info(f"saying filler message: {message}")
# NOTE: set add_to_chat_ctx=True will add the message to the end
# of the chat context of the function call for answer synthesis
speech_handle = await agent.say(message, add_to_chat_ctx=True) # noqa: F841
logger.info(f"getting weather for {latitude}, {longitude}")
url = f"https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}¤t=temperature_2m"
weather_data = {}
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
if response.status == 200:
data = await response.json()
# response from the function call is returned to the LLM
weather_data = {
"temperature": data["current"]["temperature_2m"],
"temperature_unit": "Celsius",
}
logger.info(f"weather data: {weather_data}")
else:
raise Exception(
f"Failed to get weather data, status code: {response.status}"
)
# (optional) To wait for the speech to finish before giving results of the function call
# await speech_handle.join()
return weather_data
def prewarm_process(proc: JobProcess):
# preload silero VAD in memory to speed up session start
proc.userdata["vad"] = silero.VAD.load()
async def entrypoint(ctx: JobContext):
await ctx.connect(auto_subscribe=AutoSubscribe.AUDIO_ONLY)
fnc_ctx = AssistantFnc() # create our fnc ctx instance
initial_chat_ctx = llm.ChatContext().append(
text=(
"You are a weather assistant created by LiveKit. Your interface with users will be voice. "
"You will provide weather information for a given location. "
# when using option 1, you can suppress from the agent with prompt
"do not return any text while calling the function."
# uncomment this to use option 2
# "when performing function calls, let user know that you are checking the weather."
),
role="system",
)
participant = await ctx.wait_for_participant()
agent = VoicePipelineAgent(
vad=ctx.proc.userdata["vad"],
stt=deepgram.STT(),
llm=openai.LLM(model="gpt-4o-mini"),
tts=openai.TTS(),
fnc_ctx=fnc_ctx,
chat_ctx=initial_chat_ctx,
)
# Start the assistant. This will automatically publish a microphone track and listen to the participant.
agent.start(ctx.room, participant)
await agent.say(
"Hello from the weather station. Would you like to know the weather? If so, tell me your location."
)
if __name__ == "__main__":
cli.run_app(
WorkerOptions(
entrypoint_fnc=entrypoint,
prewarm_fnc=prewarm_process,
),
)