Skip to content

Commit b0c439e

Browse files
authored
handle more status codes returned from cse-copilot (#55350)
1 parent 786305c commit b0c439e

File tree

4 files changed

+74
-38
lines changed

4 files changed

+74
-38
lines changed

data/ui.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,17 @@ search:
4949
references: Additional docs
5050
loading_status_message: Loading Copilot response...
5151
done_loading_status_message: Done loading Copilot response
52-
unable_to_answer: Sorry, I'm unable to answer that question. Check that you selected the correct GitHub version or try a different query.
5352
copy_answer: Copy answer
5453
copied_announcement: Copied!
5554
thumbs_up: This answer was helpful
5655
thumbs_down: This answer was not helpful
5756
thumbs_announcement: Thank you for your feedback!
5857
back_to_search: Back to search
58+
responses:
59+
unable_to_answer: Sorry, I'm unable to answer that question. Check that you selected the correct GitHub version or try a different question.
60+
query_too_large: Sorry, your question is too long. Please try shortening it and asking again.
61+
asked_too_many_times: Sorry, you've asked too many questions in a short time period. Please wait a few minutes and try again.
62+
invalid_query: Sorry, I'm unable to answer that question. Please try asking a different question.
5963
failure:
6064
general_title: There was an error loading search results.
6165
ai_title: There was an error loading Copilot.

src/fixtures/fixtures/data/ui.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,17 @@ search:
4949
references: Additional docs
5050
loading_status_message: Loading Copilot response...
5151
done_loading_status_message: Done loading Copilot response
52-
unable_to_answer: Sorry, I'm unable to answer that question. Check that you selected the correct GitHub version or try a different query.
5352
copy_answer: Copy answer
5453
copied_announcement: Copied!
5554
thumbs_up: This answer was helpful
5655
thumbs_down: This answer was not helpful
5756
thumbs_announcement: Thank you for your feedback!
5857
back_to_search: Back to search
58+
responses:
59+
unable_to_answer: Sorry, I'm unable to answer that question. Check that you selected the correct GitHub version or try a different question.
60+
query_too_large: Sorry, your question is too long. Please try shortening it and asking again.
61+
asked_too_many_times: Sorry, you've asked too many questions in a short time period. Please wait a few minutes and try again.
62+
invalid_query: Sorry, I'm unable to answer that question. Please try asking a different question.
5963
failure:
6064
general_title: There was an error loading search results.
6165
ai_title: There was an error loading Copilot.

src/search/components/input/AskAIResults.tsx

Lines changed: 55 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -80,27 +80,30 @@ export function AskAIResults({
8080

8181
const [conversationId, setConversationId] = useState<string>('')
8282

83-
const handleAICannotAnswer = (passedConversationId?: string) => {
83+
const handleAICannotAnswer = (
84+
passedConversationId?: string,
85+
statusCode = 400,
86+
uiMessage = t('search.ai.responses.unable_to_answer'),
87+
) => {
8488
setInitialLoading(false)
8589
setResponseLoading(false)
8690
setAICouldNotAnswer(true)
87-
const cannedResponse = t('search.ai.unable_to_answer')
8891
sendAISearchResultEvent({
8992
sources: [],
90-
message: cannedResponse,
93+
message: uiMessage,
9194
eventGroupId: askAIEventGroupId.current,
9295
couldNotAnswer: true,
93-
status: 400,
96+
status: statusCode,
9497
connectedEventId: passedConversationId || conversationId,
9598
})
96-
setMessage(cannedResponse)
97-
setAnnouncement(cannedResponse)
99+
setMessage(uiMessage)
100+
setAnnouncement(uiMessage)
98101
setReferences([])
99102
setItem(
100103
query,
101104
{
102105
query,
103-
message: cannedResponse,
106+
message: uiMessage,
104107
sources: [],
105108
aiCouldNotAnswer: true,
106109
connectedEventId: passedConversationId || conversationId,
@@ -158,17 +161,44 @@ export function AskAIResults({
158161
try {
159162
const response = await executeAISearch(router, version, query, debug)
160163
if (!response.ok) {
161-
console.error(
162-
`Failed to fetch search results.\nStatus ${response.status}\n${response.statusText}`,
163-
)
164-
sendAISearchResultEvent({
165-
sources: [],
166-
message: '',
167-
eventGroupId: askAIEventGroupId.current,
168-
couldNotAnswer: false,
169-
status: response.status,
170-
})
171-
return setAISearchError()
164+
// If there is JSON and the `upstreamStatus` key, the error is from the upstream sever (CSE)
165+
let responseJson
166+
try {
167+
responseJson = await response.json()
168+
} catch (error) {
169+
console.error('Failed to parse JSON:', error)
170+
}
171+
const upstreamStatus = responseJson?.upstreamStatus
172+
// If there is no upstream status, the error is either on our end or a 500 from CSE, so we can show the error
173+
if (!upstreamStatus) {
174+
console.error(
175+
`Failed to fetch search results.\nStatus ${response.status}\n${response.statusText}`,
176+
)
177+
sendAISearchResultEvent({
178+
sources: [],
179+
message: '',
180+
eventGroupId: askAIEventGroupId.current,
181+
couldNotAnswer: false,
182+
status: response.status,
183+
})
184+
return setAISearchError()
185+
// Query invalid - either sensitive question or spam
186+
} else if (upstreamStatus === 400 || upstreamStatus === 422) {
187+
return handleAICannotAnswer('', upstreamStatus, t('search.ai.responses.invalid_query'))
188+
// Query too large
189+
} else if (upstreamStatus === 413) {
190+
return handleAICannotAnswer(
191+
'',
192+
upstreamStatus,
193+
t('search.ai.responses.query_too_large'),
194+
)
195+
} else if (upstreamStatus === 429) {
196+
return handleAICannotAnswer(
197+
'',
198+
upstreamStatus,
199+
t('search.ai.responses.asked_too_many_times'),
200+
)
201+
}
172202
} else {
173203
setAISearchError(false)
174204
}
@@ -211,7 +241,7 @@ export function AskAIResults({
211241
return
212242
}
213243
} catch (e) {
214-
console.error(
244+
console.warn(
215245
'Failed to parse JSON:',
216246
e,
217247
'Line:',
@@ -228,7 +258,7 @@ export function AskAIResults({
228258
setConversationId(parsedLine.conversation_id)
229259
} else if (parsedLine.chunkType === 'NO_CONTENT_SIGNAL') {
230260
// Serve canned response. A question that cannot be answered was asked
231-
handleAICannotAnswer(conversationIdBuffer)
261+
handleAICannotAnswer(conversationIdBuffer, 200)
232262
} else if (parsedLine.chunkType === 'SOURCES') {
233263
if (!isCancelled) {
234264
sourcesBuffer = sourcesBuffer.concat(parsedLine.sources)
@@ -242,7 +272,11 @@ export function AskAIResults({
242272
}
243273
} else if (parsedLine.chunkType === 'INPUT_CONTENT_FILTER') {
244274
// Serve canned response. A spam question was asked
245-
handleAICannotAnswer(conversationIdBuffer)
275+
handleAICannotAnswer(
276+
conversationIdBuffer,
277+
200,
278+
t('search.ai.responses.invalid_query'),
279+
)
246280
}
247281
if (!isCancelled) {
248282
setAnnouncement('Copilot Response Loading...')

src/search/lib/ai-search-proxy.ts

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import got from 'got'
44
import { getHmacWithEpoch } from '@/search/lib/helpers/get-cse-copilot-auth'
55
import { getCSECopilotSource } from '@/search/lib/helpers/cse-copilot-docs-versions'
66

7-
const memoryCache = new Map<string, Buffer>()
8-
97
export const aiSearchProxy = async (req: Request, res: Response) => {
108
const { query, version, language } = req.body
119

@@ -43,15 +41,6 @@ export const aiSearchProxy = async (req: Request, res: Response) => {
4341
]
4442
statsd.increment('ai-search.call', 1, diagnosticTags)
4543

46-
// TODO: Caching here may cause an issue if the cache grows too large. Additionally, the cache will be inconsistent across pods
47-
const cacheKey = `${query}:${version}:${language}`
48-
if (memoryCache.has(cacheKey)) {
49-
statsd.increment('ai-search.cache_hit', 1, diagnosticTags)
50-
res.setHeader('Content-Type', 'application/x-ndjson')
51-
res.send(memoryCache.get(cacheKey))
52-
return
53-
}
54-
5544
const startTime = Date.now()
5645
let totalChars = 0
5746

@@ -84,7 +73,10 @@ export const aiSearchProxy = async (req: Request, res: Response) => {
8473
const errorMessage = `Upstream server responded with status code ${upstreamResponse.statusCode}`
8574
console.error(errorMessage)
8675
statsd.increment('ai-search.stream_response_error', 1, diagnosticTags)
87-
res.status(500).json({ errors: [{ message: errorMessage }] })
76+
res.status(upstreamResponse.statusCode).json({
77+
errors: [{ message: errorMessage }],
78+
upstreamStatus: upstreamResponse.statusCode,
79+
})
8880
stream.destroy()
8981
} else {
9082
// Set response headers
@@ -101,9 +93,11 @@ export const aiSearchProxy = async (req: Request, res: Response) => {
10193
console.error('Error streaming from cse-copilot:', error)
10294

10395
if (error?.code === 'ERR_NON_2XX_3XX_RESPONSE') {
104-
return res
105-
.status(400)
106-
.json({ errors: [{ message: 'Sorry I am unable to answer this question.' }] })
96+
const upstreamStatus = error?.response?.statusCode || 500
97+
return res.status(upstreamStatus).json({
98+
errors: [{ message: 'Upstream server error' }],
99+
upstreamStatus,
100+
})
107101
}
108102

109103
statsd.increment('ai-search.stream_error', 1, diagnosticTags)

0 commit comments

Comments
 (0)