-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathagent.py
147 lines (118 loc) · 5.03 KB
/
agent.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
139
140
141
142
143
144
145
146
147
# This is a basic agent that uses Mistral AI to answer weather questions.
# This agent is designed to be piped every single message in a Discord server.
# First, the agent checks for a location in the message, and extracts it if it exists.
# This prevents the agent from responding to messages that don't ask about weather.
# Then, a separate prompt chain is used to get the weather data and response.
import os
import json
import logging
import discord
from mistralai import Mistral
from tools.weather import seven_day_forecast
logger = logging.getLogger("discord")
MISTRAL_MODEL = "mistral-large-latest"
EXTRACT_LOCATION_PROMPT = """
Is this message explicitly requesting weather information for a specific city/location?
If not, return {"location": "none"}.
Otherwise, return the full name of the city in JSON format.
Example:
Message: What's the weather in sf?
Response: {"location": "San Francisco, CA"}
Message: What's the temperature in nyc?
Response: {"location": "New York City, NY"}
Message: Is it raining in sf?
Response: {"location": "San Francisco, CA"}
Message: I love the weather in SF
Response: {"location": "none"}
"""
TOOLS_PROMPT = """
You are a helpful weather assistant.
Given a location and a user's request, use your tools to fulfill the request.
Only use tools if needed. If you use a tool, make sure the longitude is correctly negative or positive
Provide a short, concise answer that uses emojis.
"""
class WeatherAgent:
def __init__(self):
MISTRAL_API_KEY = os.getenv("MISTRAL_API_KEY")
self.client = Mistral(api_key=MISTRAL_API_KEY)
self.tools = [
{
"type": "function",
"function": {
"name": "seven_day_forecast",
"description": "Get the seven day forecast for a given location with latitude and longitude.",
"parameters": {
"type": "object",
"properties": {
"latitude": {"type": "string"},
"longitude": {"type": "string"},
},
"required": ["latitude", "longitude"],
},
},
}
]
self.tools_to_functions = {
"seven_day_forecast": seven_day_forecast,
}
async def extract_location(self, message: str) -> dict:
# Extract the location from the message.
response = await self.client.chat.complete_async(
model=MISTRAL_MODEL,
messages=[
{"role": "system", "content": EXTRACT_LOCATION_PROMPT},
{"role": "user", "content": f"Discord message: {message}\nOutput:"},
],
response_format={"type": "json_object"},
)
message = response.choices[0].message.content
obj = json.loads(message)
if obj["location"] == "none":
return None
return obj["location"]
async def get_weather_with_tools(self, location: str, request: str):
messages = [
{"role": "system", "content": TOOLS_PROMPT},
{
"role": "user",
"content": f"Location: {location}\nRequest: {request}\nOutput:",
},
]
# Require the agent to use a tool with the "any" tool choice.
tool_response = await self.client.chat.complete_async(
model=MISTRAL_MODEL,
messages=messages,
tools=self.tools,
tool_choice="any",
)
messages.append(tool_response.choices[0].message)
tool_call = tool_response.choices[0].message.tool_calls[0]
function_name = tool_call.function.name
function_params = json.loads(tool_call.function.arguments)
function_result = self.tools_to_functions[function_name](**function_params)
# Append the tool call and its result to the messages.
messages.append(
{
"role": "tool",
"name": function_name,
"content": function_result,
"tool_call_id": tool_call.id,
}
)
# Run the model again with the tool call and its result.
response = await self.client.chat.complete_async(
model=MISTRAL_MODEL,
messages=messages,
)
return response.choices[0].message.content
async def run(self, message: discord.Message):
# Extract the location from the message to verify that the user is asking about weather in a specific location.
location = await self.extract_location(message.content)
if location is None:
return None
# Send a message to the user that we are fetching weather data.
res_message = await message.reply(f"Fetching weather for {location}...")
# Use a second prompt chain to get the weather data and response.
weather_response = await self.get_weather_with_tools(location, message.content)
# Edit the message to show the weather data.
await res_message.edit(content=weather_response)