Skip to content

Commit ac2932f

Browse files
Handle FAQ assistant outages
1 parent 9e7c75e commit ac2932f

2 files changed

Lines changed: 93 additions & 1 deletion

File tree

automator/src/lambda_function.py

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@
1818
FAQ_ASSISTANT_URL = os.getenv('FAQ_ASSISTANT_URL', '').strip()
1919
FAQ_ASSISTANT_SHARED_SECRET = os.getenv('FAQ_ASSISTANT_SHARED_SECRET', '')
2020
FAQ_ASSISTANT_TIMEOUT = int(os.getenv('FAQ_ASSISTANT_TIMEOUT', '55'))
21+
FAQ_ASSISTANT_ERROR_MESSAGE = (
22+
'The FAQ assistant is currently not available. '
23+
'In the meantime, you can try to find the answer here:'
24+
)
25+
FAQ_ASSISTANT_GENERIC_RESOURCES = {
26+
'docs': 'https://datatalks.club/docs/',
27+
'faq': 'https://datatalks.club/faq/',
28+
}
2129

2230

2331

@@ -93,6 +101,33 @@ def call_faq_assistant(payload):
93101
return response.json()
94102

95103

104+
def faq_assistant_fallback_resources(payload):
105+
course = payload.get('course')
106+
if not course:
107+
return FAQ_ASSISTANT_GENERIC_RESOURCES
108+
109+
return {
110+
'docs': f'https://datatalks.club/docs/courses/{course}/',
111+
'faq': f'https://datatalks.club/faq/{course}.html',
112+
'repo': f'https://github.com/DataTalksClub/{course}',
113+
}
114+
115+
116+
def format_faq_assistant_error_message(payload):
117+
resources = faq_assistant_fallback_resources(payload)
118+
lines = [
119+
f"- <{resources['docs']}|Docs>",
120+
f"- <{resources['faq']}|FAQ>",
121+
]
122+
if resources.get('repo'):
123+
lines.append(f"- <{resources['repo']}|Course repo>")
124+
125+
return (
126+
f'{FAQ_ASSISTANT_ERROR_MESSAGE}\n\n'
127+
+ '\n'.join(lines)
128+
)
129+
130+
96131
def post_faq_assistant_answer(channel, thread_ts, payload):
97132
if not channel or not thread_ts:
98133
logger.info('FAQ assistant request missing channel or ts')
@@ -109,7 +144,21 @@ def post_faq_assistant_answer(channel, thread_ts, payload):
109144
f"FAQ assistant request: scope={payload['scope']} "
110145
f"course={payload.get('course', '')}"
111146
)
112-
answer = call_faq_assistant(payload)
147+
try:
148+
answer = call_faq_assistant(payload)
149+
except requests.RequestException as exc:
150+
response = getattr(exc, 'response', None)
151+
status = getattr(response, 'status_code', None)
152+
body = getattr(response, 'text', '') if response is not None else ''
153+
logger.info(
154+
f"FAQ assistant request failed: status={status} "
155+
f"error={exc} body={body[:500]}"
156+
)
157+
slack.post_message_to_thread(
158+
channel, thread_ts, format_faq_assistant_error_message(payload)
159+
)
160+
return
161+
113162
if not answer:
114163
return
115164

automator/tests/test_automator_lambda.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,6 +1103,49 @@ def test_handle_app_mention_appends_sources(self, mock_call, mock_slack):
11031103
self.assertIn('*Sources:*', posted)
11041104
self.assertIn('<https://datatalks.club/faq/llm-zoomcamp.html#abc|Joining>', posted)
11051105

1106+
@patch('automator_lambda_function.slack')
1107+
@patch('automator_lambda_function.call_faq_assistant')
1108+
def test_faq_assistant_failure_posts_error_without_raising(self, mock_call, mock_slack):
1109+
response = MagicMock()
1110+
response.status_code = 500
1111+
response.text = 'upstream exploded'
1112+
mock_call.side_effect = lambda_function.requests.exceptions.HTTPError(
1113+
'500 Server Error', response=response
1114+
)
1115+
1116+
lambda_function.post_faq_assistant_answer(
1117+
'C06TEGTGM3J',
1118+
'1790000000.000100',
1119+
{
1120+
'question': 'Can I still join?',
1121+
'scope': 'course',
1122+
'course': 'llm-zoomcamp',
1123+
},
1124+
)
1125+
1126+
mock_slack.github_to_slack_markdown.assert_not_called()
1127+
posted = mock_slack.post_message_to_thread.call_args[0][2]
1128+
self.assertIn('The FAQ assistant is currently not available.', posted)
1129+
self.assertIn('In the meantime, you can try to find the answer here:', posted)
1130+
self.assertIn('<https://datatalks.club/docs/courses/llm-zoomcamp/|Docs>', posted)
1131+
self.assertIn('<https://datatalks.club/faq/llm-zoomcamp.html|FAQ>', posted)
1132+
self.assertIn('<https://github.com/DataTalksClub/llm-zoomcamp|Course repo>', posted)
1133+
mock_slack.post_message_to_thread.assert_called_once_with(
1134+
'C06TEGTGM3J',
1135+
'1790000000.000100',
1136+
posted,
1137+
)
1138+
1139+
def test_faq_assistant_error_message_uses_generic_resources_without_course(self):
1140+
message = lambda_function.format_faq_assistant_error_message({
1141+
'question': 'How do I join Slack?',
1142+
'scope': 'docs',
1143+
})
1144+
1145+
self.assertIn('<https://datatalks.club/docs/|Docs>', message)
1146+
self.assertIn('<https://datatalks.club/faq/|FAQ>', message)
1147+
self.assertNotIn('Course repo', message)
1148+
11061149
def test_faq_reaction_config_uses_faq_assistant(self):
11071150
reaction_config = lambda_function.reaction_configs.get('faq')
11081151
self.assertIsNotNone(reaction_config)

0 commit comments

Comments
 (0)