Skip to content

Commit 9ea2963

Browse files
authored
add ghes support to Copilot Search (#55487)
1 parent d9c4f11 commit 9ea2963

File tree

8 files changed

+36
-123
lines changed

8 files changed

+36
-123
lines changed

src/events/components/experiments/experiments.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,6 @@ export const EXPERIMENTS = {
2424
percentOfUsersToGetExperiment: 0, // 10% of users will get the experiment
2525
includeVariationInContext: true, // All events will include the `experiment_variation` of the `ai_search_experiment`
2626
limitToLanguages: ['en'], // Only users with the `en` language will be included in the experiment
27-
limitToVersions: [
28-
'free-pro-team@latest',
29-
'enterprise-cloud@latest',
30-
'enterprise-server@latest',
31-
], // Only enable for versions
3227
alwaysShowForStaff: true, // When set to true, staff will always see the experiment (determined by the `staffonly` cookie)
3328
turnOnWithURLParam: 'ai_search', /// When the query param `?feature=ai_search` is set, the experiment will be enabled
3429
},

src/search/components/helpers/execute-search-actions.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,18 +45,10 @@ export function executeGeneralSearch(
4545
router.push(asPath, undefined, { shallow: false })
4646
}
4747

48-
export async function executeAISearch(
49-
router: NextRouter,
50-
version: string,
51-
query: string,
52-
debug = false,
53-
) {
54-
let language = router.locale || 'en'
55-
48+
export async function executeAISearch(version: string, query: string, debug = false) {
5649
const body = {
5750
query,
5851
version,
59-
language,
6052
...(debug && { debug: '1' }),
6153
}
6254

src/search/components/input/AskAIResults.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ export function AskAIResults({
167167
let conversationIdBuffer = ''
168168

169169
try {
170-
const response = await executeAISearch(router, version, query, debug)
170+
const response = await executeAISearch(version, query, debug)
171171
if (!response.ok) {
172172
// If there is JSON and the `upstreamStatus` key, the error is from the upstream sever (CSE)
173173
let responseJson

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

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { Request, Response } from 'express'
1+
import { Response } from 'express'
22
import statsd from '@/observability/lib/statsd'
33
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'
6+
import type { ExtendedRequest } from '@/types'
67

7-
export const aiSearchProxy = async (req: Request, res: Response) => {
8-
const { query, version, language } = req.body
8+
export const aiSearchProxy = async (req: ExtendedRequest, res: Response) => {
9+
const { query, version } = req.body
910

1011
const errors = []
1112

@@ -15,18 +16,12 @@ export const aiSearchProxy = async (req: Request, res: Response) => {
1516
} else if (typeof query !== 'string') {
1617
errors.push({ message: `Invalid 'query' in request body. Must be a string` })
1718
}
18-
if (!version) {
19-
errors.push({ message: `Missing required key 'version' in request body` })
20-
}
21-
if (!language) {
22-
errors.push({ message: `Missing required key 'language' in request body` })
23-
}
2419

2520
let docsSource = ''
2621
try {
27-
docsSource = getCSECopilotSource(version, language)
22+
docsSource = getCSECopilotSource(version)
2823
} catch (error: any) {
29-
errors.push({ message: error?.message || 'Invalid version or language' })
24+
errors.push({ message: error?.message || 'Invalid version' })
3025
}
3126

3227
if (errors.length) {
@@ -36,7 +31,7 @@ export const aiSearchProxy = async (req: Request, res: Response) => {
3631

3732
const diagnosticTags = [
3833
`version:${version}`.slice(0, 200),
39-
`language:${language}`.slice(0, 200),
34+
`language:${req.language}`.slice(0, 200),
4035
`queryLength:${query.length}`.slice(0, 200),
4136
]
4237
statsd.increment('ai-search.call', 1, diagnosticTags)
@@ -52,7 +47,8 @@ export const aiSearchProxy = async (req: Request, res: Response) => {
5247
}
5348

5449
try {
55-
const stream = got.stream.post(`${process.env.CSE_COPILOT_ENDPOINT}/answers`, {
50+
// TODO: We temporarily add ?ai_search=1 to use a new pattern in cgs-copilot production
51+
const stream = got.stream.post(`${process.env.CSE_COPILOT_ENDPOINT}/answers?ai_search=1`, {
5652
json: body,
5753
headers: {
5854
Authorization: getHmacWithEpoch(),

src/search/lib/elasticsearch-indexes.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,15 @@ export function getElasticSearchIndex(
7474
}
7575

7676
// e.g. free-pro-team becomes fpt for the index name
77-
const indexVersion = versionToIndexVersionMap[version]
77+
let indexVersion = versionToIndexVersionMap[version]
78+
79+
// TODO: For AI Search, we initially only supported the latest GHES version
80+
// Supporting more versions would involve adding more indexes and generating the data to fill them
81+
// As a work around, we will just use the latest version for all GHES suggestions / autocomplete
82+
// This is a temporary fix until we can support more versions
83+
if (type === 'aiSearchAutocomplete' && indexVersion.startsWith('ghes')) {
84+
indexVersion = versionToIndexVersionMap['enterprise-server']
85+
}
7886

7987
// In the index-test-fixtures.sh script, we use the tests_ prefix index for testing
8088
const testPrefix = process.env.NODE_ENV === 'test' ? 'tests_' : ''
Lines changed: 11 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
// Versions used by cse-copilot
2-
import { allVersions } from '@/versions/lib/all-versions'
32
import { versionToIndexVersionMap } from '../elasticsearch-versions'
43
const CSE_COPILOT_DOCS_VERSIONS = ['dotcom', 'ghec', 'ghes']
54

@@ -9,70 +8,22 @@ export function supportedCSECopilotLanguages() {
98
return DOCS_LANGUAGES
109
}
1110

12-
export function getCSECopilotSource(
13-
version: (typeof CSE_COPILOT_DOCS_VERSIONS)[number],
14-
language: (typeof DOCS_LANGUAGES)[number],
15-
) {
16-
const mappedVersion = versionToIndexVersionMap[version]
17-
const { cseCopilotDocsVersion, ghesButNotLatest } = getVersionInfo(mappedVersion)
11+
export function getCSECopilotSource(version: (typeof CSE_COPILOT_DOCS_VERSIONS)[number]) {
12+
if (!version) {
13+
throw new Error(`Missing required key 'version' in request body`)
14+
}
1815

19-
if (ghesButNotLatest) {
20-
throw new Error(
21-
`Only the latest version of GHES is supported for cse-copilot queries. Please use 'ghes@latest'`,
22-
)
16+
let mappedVersion = versionToIndexVersionMap[version]
17+
// CSE-Copilot uses 'dotcom' as the version name for free-pro-team
18+
if (mappedVersion === 'fpt') {
19+
mappedVersion = 'dotcom'
2320
}
2421

25-
if (!CSE_COPILOT_DOCS_VERSIONS.includes(cseCopilotDocsVersion)) {
22+
if (!CSE_COPILOT_DOCS_VERSIONS.includes(mappedVersion) && !mappedVersion?.startsWith('ghes-')) {
2623
throw new Error(
2724
`Invalid 'version' in request body: '${version}'. Must be one of: ${CSE_COPILOT_DOCS_VERSIONS.join(', ')}`,
2825
)
2926
}
30-
if (!DOCS_LANGUAGES.includes(language)) {
31-
throw new Error(
32-
`Invalid 'language' in request body '${language}'. Must be one of: ${DOCS_LANGUAGES.join(', ')}`,
33-
)
34-
}
35-
// cse-copilot uses version names in the form `docs_<shortName>_<language>`, e.g. `docs_ghes_en`
36-
return `docs_${cseCopilotDocsVersion}_${language}`
37-
}
38-
39-
function getVersionInfo(Version: string): {
40-
cseCopilotDocsVersion: string
41-
ghesButNotLatest: boolean
42-
} {
43-
const versionObject = Object.values(allVersions).find(
44-
(info) =>
45-
info.shortName === Version ||
46-
info.plan === Version ||
47-
info.miscVersionName === Version ||
48-
info.currentRelease === Version,
49-
)
50-
51-
let cseCopilotDocsVersion = versionObject?.shortName || ''
52-
let ghesButNotLatest = false
53-
if (!versionObject || !cseCopilotDocsVersion) {
54-
return {
55-
cseCopilotDocsVersion,
56-
ghesButNotLatest,
57-
}
58-
}
59-
60-
// CSE-Copilot uses 'dotcom' as the version name for free-pro-team
61-
if (cseCopilotDocsVersion === 'fpt') {
62-
cseCopilotDocsVersion = 'dotcom'
63-
}
64-
65-
// If ghes, we only support the latest version for cse-copilot queries
66-
// Since that's the only version cse-copilot scrapes from our docs
67-
if (
68-
versionObject.shortName === 'ghes' &&
69-
versionObject.currentRelease !== versionObject.latestRelease
70-
) {
71-
ghesButNotLatest = true
72-
}
73-
74-
return {
75-
cseCopilotDocsVersion,
76-
ghesButNotLatest,
77-
}
27+
// cse-copilot uses version names in the form `docs_<version-shortName>`, e.g. `docs_ghes-3.16`
28+
return `docs_${mappedVersion}`
7829
}

src/search/lib/search-request-params/search-params-objects.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import languages from '@/languages/lib/languages'
77
import { allIndexVersionKeys, versionToIndexVersionMap } from '@/search/lib/elasticsearch-versions'
88
import { SearchTypes } from '@/search/types'
9-
import { latest } from '@/versions/lib/enterprise-server-releases'
109

1110
import type { SearchRequestQueryParams } from '@/search/lib/search-request-params/types'
1211

@@ -120,20 +119,14 @@ const SHARED_AUTOCOMPLETE_PARAMS_OBJ: SearchRequestQueryParams[] = [
120119
cast: (size: string) => parseInt(size, 10),
121120
validate: (size: number) => size >= 0 && size <= MAX_AUTOCOMPLETE_SIZE,
122121
},
123-
// We only want to enable for latest versions of fpt, ghec, and ghes
124122
{
125123
key: 'version',
126124
default_: 'free-pro-team',
127125
validate: (version: string) => {
128-
const mappedVersion = versionToIndexVersionMap[version]
129-
if (
130-
mappedVersion === 'fpt' ||
131-
mappedVersion === 'ghec' ||
132-
mappedVersion === `ghes-${latest}`
133-
) {
134-
return true
126+
if (!versionToIndexVersionMap[version]) {
127+
throw new ValidationError(`'${version}' not in ${allIndexVersionKeys.join(', ')}`)
135128
}
136-
return false
129+
return true
137130
},
138131
},
139132
]

src/search/tests/api-ai-search.ts

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -94,24 +94,8 @@ describe('AI Search Routes', () => {
9494
])
9595
})
9696

97-
test('should handle validation errors: language missing', async () => {
98-
let body = { query: 'example query', version: 'dotcom' }
99-
const response = await post('/api/ai-search/v1', {
100-
body: JSON.stringify(body),
101-
headers: { 'Content-Type': 'application/json' },
102-
})
103-
104-
const responseBody = JSON.parse(response.body)
105-
106-
expect(response.ok).toBe(false)
107-
expect(responseBody['errors']).toEqual([
108-
{ message: `Missing required key 'language' in request body` },
109-
{ message: `Invalid 'language' in request body 'undefined'. Must be one of: en` },
110-
])
111-
})
112-
11397
test('should handle validation errors: version missing', async () => {
114-
let body = { query: 'example query', language: 'en' }
98+
let body = { query: 'example query' }
11599
const response = await post('/api/ai-search/v1', {
116100
body: JSON.stringify(body),
117101
headers: { 'Content-Type': 'application/json' },
@@ -122,13 +106,10 @@ describe('AI Search Routes', () => {
122106
expect(response.ok).toBe(false)
123107
expect(responseBody['errors']).toEqual([
124108
{ message: `Missing required key 'version' in request body` },
125-
{
126-
message: `Invalid 'version' in request body: 'undefined'. Must be one of: dotcom, ghec, ghes`,
127-
},
128109
])
129110
})
130111

131-
test('should handle multiple validation errors: query missing, invalid language and version', async () => {
112+
test('should handle multiple validation errors: query missing and version', async () => {
132113
let body = { language: 'fr', version: 'fpt' }
133114
const response = await post('/api/ai-search/v1', {
134115
body: JSON.stringify(body),
@@ -140,9 +121,6 @@ describe('AI Search Routes', () => {
140121
expect(response.ok).toBe(false)
141122
expect(responseBody['errors']).toEqual([
142123
{ message: `Missing required key 'query' in request body` },
143-
{
144-
message: `Invalid 'language' in request body 'fr'. Must be one of: en`,
145-
},
146124
])
147125
})
148126
})

0 commit comments

Comments
 (0)