-
Notifications
You must be signed in to change notification settings - Fork 0
/
service_alerts_bot.py
155 lines (117 loc) · 4.89 KB
/
service_alerts_bot.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
148
149
150
151
152
153
154
155
from datetime import datetime, timedelta
import json
import typing
import boto3
import openai
import requests
from coct_mastodon_bots.mastodon_bot_utils import init_mastodon_client, TOOT_MAX_LENGTH
SERVICE_ALERT_BUCKET = "coct-service-alerts-bot"
SERVICE_ALERT_PREFIX = "alerts"
CHATGPT_TEMPLATE = """
Please draft a toot about a potential City of Cape Town service outage or update on an outage in a concerned and
helpful tone, using the details in the following JSON. The "service_area" field refers to the responsible department.
{json_str}
Keep it strictly under {toot_length} chars in length. Only return the content of the toot.
"""
REQUEST_RETRIES = 3
REQUEST_TIMEOUT = 60
ALERTS_TEMPLATE = "https://service-alerts.cct-datascience.xyz/alerts/{alert_id}.json"
TOOT_TEMPLATE = """{answer_str}
Content generated automatically from {link_str}"""
s3 = boto3.client('s3')
http_session = requests.Session()
def _convert_to_sast_str(utc_str: str) -> str:
return (
datetime.strptime(utc_str[:-5], "%Y-%m-%dT%H:%M:%S") + timedelta(hours=2)
).strftime("%Y-%m-%dT%H:%M:%S") + "+02:00"
def _chatgpt_wrapper(message: str, max_response_length: int) -> str:
rough_token_count = len(message) // 4 + 256
temperature = 0.2
last_error = None
for t in range(REQUEST_RETRIES):
response_message = None
try:
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{"role": "user", "content": message},
],
temperature=temperature,
max_tokens=4097 - rough_token_count,
timeout=REQUEST_TIMEOUT
)
response_message = response['choices'][0]['message']['content']
# Checking response length is right
assert len(response_message) <= max_response_length, "message is too long!"
return response_message
except Exception as e:
print(f"Got {e.__class__.__name__}: {e}")
print(f"try: {t + 1}/3")
print(f"{response_message=}")
if isinstance(e, openai.error.InvalidRequestError):
print("increasing token count")
rough_token_count *= 1.2
rough_token_count = int(rough_token_count)
else:
temperature += 0.2
last_error = e
else:
raise last_error
def _generate_toot_from_chatgpt(alert: typing.Dict, alert_id: str, alert_filename: str) -> str:
# Removing a few fields which often confuse ChatGPT
for field in ('Id', 'publish_date', 'effective_date', 'expiry_date', 'tweet_text', 'toot_text'):
del alert[field]
# Also, removing any null items
keys_to_delete = [
k for k, v in alert.items()
if v is None
]
for k in keys_to_delete:
del alert[k]
# converting the timezone values to SAST
for ts in ("start_timestamp", "forecast_end_timestamp"):
alert[ts] = _convert_to_sast_str(alert[ts])
# Trying to get text from ChatGPT
try:
gpt_template = CHATGPT_TEMPLATE.format(json_str=json.dumps(alert), )
gpt_template += (
" . Encourage the use of the request_number value when contacting the City"
if "request_number" in alert else ""
)
# Getting tweet text from ChatGPT
message = _chatgpt_wrapper(gpt_template, TOOT_MAX_LENGTH)
except Exception as e:
# Failing with a sensible message
print(f"Failed to generate toot text for '{alert_id}' because {e.__class__.__name__}")
message = None
return message
def lambda_handler(event, context):
record, *_ = event['Records']
sns_message = record['Sns']['Message']
data = json.loads(sns_message)
print(f"{len(data)=}")
mastodon = init_mastodon_client()
for service_alert in data:
service_alert_id = service_alert['Id']
service_alert_filename = f"{service_alert_id}.json"
# try load message from v1 endpoint
message = None
service_alert_path = ALERTS_TEMPLATE.format(alert_id=service_alert_id)
if requests.head(service_alert_path).status_code == 200:
service_alert_data = http_session.get(service_alert_path).json()
message = service_alert_data.get("toot_text", None)
if message:
print("Using cptgpt text")
# Falling back to ChatGPT if there isn't anything from CPTGPT
if message is None:
message = _generate_toot_from_chatgpt(service_alert, service_alert_id, service_alert_filename)
# Generating final toot
toot = TOOT_TEMPLATE.format(
answer_str=message if message else "Content failed to generate. Please consult link below",
link_str=service_alert_path
)
# All done, posting to Mastodon
mastodon.status_post(toot)
return {
'statusCode': 200,
}