Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
878907c
adding aggregation generation method
shanbady Apr 8, 2026
d011419
adding aggregations to response
shanbady Apr 9, 2026
8e9aa44
adding some optimizations and aggregations to response
shanbady Apr 9, 2026
f36d1cb
regen spec
shanbady Apr 9, 2026
ab34a23
add published back to learning resources serializer
shanbady Apr 9, 2026
22c8d43
spec update
shanbady Apr 9, 2026
518c5a1
show facets on frontend
shanbady Apr 9, 2026
73a080a
fixing aggregation counts
shanbady Apr 10, 2026
f3168d6
fix test
shanbady Apr 10, 2026
937ddf7
fix typechecks
shanbady Apr 10, 2026
1c1ea40
remove unused test
shanbady Apr 10, 2026
11dfdc8
adding tests for aggregations
shanbady Apr 13, 2026
23d9bb4
Update vector_search/serializers.py
shanbady Apr 13, 2026
7abd6f9
Update vector_search/views.py
shanbady Apr 13, 2026
45a3011
fixing 'with_payload' for group by
shanbady Apr 14, 2026
ab4c0f8
switch to safe getter
shanbady Apr 14, 2026
d310643
correct comment about dropping admin params
shanbady Apr 14, 2026
ef6c227
switching collection param map to constant
shanbady Apr 14, 2026
3e1ed7a
adding aggregation params for contentfiles
shanbady Apr 14, 2026
d09c4b5
regenerate spec
shanbady Apr 14, 2026
493fc35
adding fix for hybrid search offset
shanbady Apr 14, 2026
c1b848f
fix tests for new expected response
shanbady Apr 14, 2026
de908ef
fix contentfile metadata
shanbady Apr 14, 2026
2668365
make hits and get_results same for both serializers
shanbady Apr 14, 2026
a17581b
fixing skip with relation to offsets
shanbady Apr 14, 2026
15663a3
tune prefetch multiplier
shanbady Apr 14, 2026
7d1c544
gather count with hits
shanbady Apr 14, 2026
9a8f7db
adding fix for fields returned by contentfile endpoint
shanbady Apr 15, 2026
803baf0
Merge branch 'main' into shanbady/vector-search-facets
shanbady Apr 15, 2026
027114c
default hits to list
shanbady Apr 15, 2026
d654892
Update vector_search/utils.py
shanbady Apr 16, 2026
f58178d
Merge branch 'main' into shanbady/vector-search-facets
shanbady Apr 16, 2026
13c0593
restore and update js test for vector hybrid search facet results
shanbady Apr 16, 2026
2348970
move published to resource specific serializer field
shanbady Apr 16, 2026
7fa59dd
update spec
shanbady Apr 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions frontends/api/src/generated/v0/api.ts

Large diffs are not rendered by default.

101 changes: 57 additions & 44 deletions frontends/main/src/app-pages/SearchPage/SearchPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,50 +152,6 @@ describe("SearchPage", () => {
},
)

test("Vector Hybrid Search passes correct params and hides count", async () => {
setMockApiResponses({
search: {
count: 700,
metadata: {
aggregations: {
resource_type_group: [{ key: "course", doc_count: 100 }],
},
suggestions: [],
},
results: factories.learningResources.resources({ count: 5 }).results,
},
})

// Authenticate as path editor (admin)
setMockResponse.get(urls.userMe.get(), {
is_learning_path_editor: true,
is_authenticated: true,
})

renderWithProviders(<SearchPage />, { url: "?vector_search=true&q=test" })

await waitFor(() => {
const call = makeRequest.mock.calls.find(([_method, url]) => {
return url.includes(urls.search.vectorResources())
})
expect(call).toBeDefined()
})

const call = makeRequest.mock.calls.find(([_method, url]) =>
url.includes(urls.search.vectorResources()),
)
invariant(call)
const fullUrl = new URL(call[1], "http://mit.edu")
const apiSearchParams = fullUrl.searchParams

expect(apiSearchParams.get("hybrid_search")).toBe("true")
expect(apiSearchParams.get("q")).toBe("test")

// Ensure count is hidden
const hideCountText = screen.queryByText("700 results")
expect(hideCountText).toBeNull()
})

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with the copilot review, probably better to update this test rather than remove it altogether, if possible.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

restored and updated

test("Toggling facets", async () => {
setMockApiResponses({
Comment on lines 155 to 156

Copilot AI Apr 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR removes the test covering vector search request construction/behavior. Since vector search params now include aggregations (and the count UI behavior changed), this should be updated rather than deleted to preserve coverage that the correct endpoint/params are used and the UI renders expected count/facets.

Copilot generated this review using guidance from repository custom instructions.
search: {
Expand Down Expand Up @@ -1060,4 +1016,61 @@ describe("UniversalAIBanner", () => {
expect(screen.queryByText("Universal AI")).not.toBeInTheDocument()
expect(screen.queryByText("New on MIT Learn")).not.toBeInTheDocument()
})

test("Vector Hybrid Search passes correct params and renders expected count/facets", async () => {
setMockApiResponses({
search: {
count: 700,
metadata: {
aggregations: {
resource_type_group: [{ key: "course", doc_count: 100 }],
},
suggestions: [],
},
results: factories.learningResources.resources({ count: 5 }).results,
},
})

// Authenticate as path editor (admin)
setMockResponse.get(urls.userMe.get(), {
is_learning_path_editor: true,
is_authenticated: true,
})

renderWithProviders(<SearchPage />, { url: "?vector_search=true&q=test" })

await waitFor(() => {
const call = makeRequest.mock.calls.find(([_method, url]) => {
return url.includes(urls.search.vectorResources())
})
expect(call).toBeDefined()
})

const call = makeRequest.mock.calls.find(([_method, url]) =>
url.includes(urls.search.vectorResources()),
)
invariant(call)
const fullUrl = new URL(call[1], "http://mit.edu")
const apiSearchParams = fullUrl.searchParams

expect(apiSearchParams.get("hybrid_search")).toBe("true")
expect(apiSearchParams.get("q")).toBe("test")

// Ensure count is visible
const countText = await screen.findByText("700 results")
expect(countText).toBeVisible()

// Ensure facets are visible
await waitFor(() => {
const tabs = screen.getAllByRole("tab")
expect(
tabs.map((tab) => (tab.textContent || "").replace(/\s/g, "")),
).toEqual([
"All(100)",
"Courses(100)",
"Programs(0)",
"LearningMaterials(0)",
])
})
})
})
18 changes: 10 additions & 8 deletions frontends/main/src/page-components/SearchDisplay/SearchDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -516,8 +516,8 @@ const searchModeDropdownOptions = Object.entries(

/**
* Extracts only the fields supported by the vector search API from a broader
* search params object, dropping admin-only params (e.g., aggregations,
* content_file_score_weight) that the vector endpoint does not accept.
* search params object, dropping admin-only params (e.g., content_file_score_weight)
* that the vector endpoint does not accept.
*
* The `as` casts for enum arrays are safe because the v0 and v1 generated
* clients define separate (but structurally identical) enum types for the same
Expand All @@ -526,6 +526,7 @@ const searchModeDropdownOptions = Object.entries(
const toVectorSearchParams = (
params: ReturnType<typeof getSearchParams>,
): VectorSearchRequest => ({
aggregations: params.aggregations as VectorSearchRequest["aggregations"],
certification: params.certification,
certification_type:
params.certification_type as VectorSearchRequest["certification_type"],
Expand Down Expand Up @@ -625,10 +626,13 @@ const SearchDisplay: React.FC<SearchDisplayProps> = ({
const wantsVectorSearch = searchParams.get("vector_search") === "true"
const isVectorSearch = wantsVectorSearch && user?.is_learning_path_editor

const queryOptions = isVectorSearch
? learningResourceQueries.vectorSearch(toVectorSearchParams(allParams))
: learningResourceQueries.search(allParams as LRSearchRequest)

// @ts-expect-error Typescript has trouble unifying the different query key types
const { data, isLoading, isFetching } = useQuery({
...(isVectorSearch
? learningResourceQueries.vectorSearch(toVectorSearchParams(allParams))
: learningResourceQueries.search(allParams as LRSearchRequest)),
...queryOptions,
enabled: !wantsVectorSearch || !isUserLoading,
placeholderData: keepPreviousData,
select: (timedData: {
Expand Down Expand Up @@ -985,9 +989,7 @@ const SearchDisplay: React.FC<SearchDisplayProps> = ({
* the count when data is loaded even if count is same as previous
* count.
*/}
{isFetching || isLoading || isVectorSearch
? ""
: `${data?.count} results`}
{isFetching || isLoading ? "" : `${data?.count} results`}
</VisuallyHidden>
<UniversalAIBanner searchParams={searchParams} />
<Stack direction="row" justifyContent="space-between">
Expand Down
4 changes: 2 additions & 2 deletions main/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -822,10 +822,10 @@ def get_all_config_keys():
QDRANT_CLIENT_TIMEOUT = get_int(name="QDRANT_CLIENT_TIMEOUT", default=10)

VECTOR_HYBRID_SEARCH_PREFETCH_MULTIPLIER = get_int(
name="VECTOR_HYBRID_SEARCH_PREFETCH_MULTIPLIER", default=20
name="VECTOR_HYBRID_SEARCH_PREFETCH_MULTIPLIER", default=5
)
VECTOR_HYBRID_SEARCH_PREFETCH_MAX_LIMIT = get_int(
name="VECTOR_HYBRID_SEARCH_PREFETCH_MAX_LIMIT", default=10000
name="VECTOR_HYBRID_SEARCH_PREFETCH_MAX_LIMIT", default=500
)
Comment on lines 824 to 829

Copilot AI Apr 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The defaults for VECTOR_HYBRID_SEARCH_PREFETCH_MULTIPLIER (20 → 5) and VECTOR_HYBRID_SEARCH_PREFETCH_MAX_LIMIT (10000 → 500) change hybrid vector search behavior globally and may affect result quality/recall. If this is an intentional tuning change, it would help to document the rationale (or keep the previous defaults and rely on env overrides) to avoid surprising behavior changes across environments.

Copilot uses AI. Check for mistakes.
# toggle to use requests (default for local) or webdriver which renders js elements
EMBEDDINGS_EXTERNAL_FETCH_USE_WEBDRIVER = get_bool(
Expand Down
114 changes: 114 additions & 0 deletions openapi/specs/v0.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,58 @@ paths:
description: Vector Search for content
summary: Content File Vector Search
parameters:
- in: query
name: aggregations
schema:
type: array
items:
enum:
- key
- course_number
- platform
- offered_by
- file_extension
- content_feature_type
- run_readable_id
- resource_readable_id
- run_title
- edx_module_id
- content_type
- description
- title
- url
- file_type
- summary
- flashcards
- checksum
type: string
description: |-
* `key` - Key
* `course_number` - Course Number
* `platform` - Platform
* `offered_by` - Offered By
* `file_extension` - File Extension
* `content_feature_type` - Content Feature Type
* `run_readable_id` - Run Readable Id
* `resource_readable_id` - Resource Readable Id
* `run_title` - Run Title
* `edx_module_id` - Edx Module Id
* `content_type` - Content Type
* `description` - Description
* `title` - Title
* `url` - Url
* `file_type` - File Type
* `summary` - Summary
* `flashcards` - Flashcards
* `checksum` - Checksum
description: "aggregations for facet counts \n\n* `key` - Key\n\
* `course_number` - Course Number\n* `platform` - Platform\n* `offered_by`\
\ - Offered By\n* `file_extension` - File Extension\n* `content_feature_type`\
\ - Content Feature Type\n* `run_readable_id` - Run Readable Id\n* `resource_readable_id`\
\ - Resource Readable Id\n* `run_title` - Run Title\n* `edx_module_id` -\
\ Edx Module Id\n* `content_type` - Content Type\n* `description` - Description\n\
* `title` - Title\n* `url` - Url\n* `file_type` - File Type\n* `summary`\
\ - Summary\n* `flashcards` - Flashcards\n* `checksum` - Checksum"
- in: query
name: collection_name
schema:
Expand Down Expand Up @@ -961,6 +1013,61 @@ paths:
description: Vector Search for learning resources
summary: Vector Search
parameters:
- in: query
name: aggregations
schema:
type: array
items:
enum:
- readable_id
- resource_type
- certification
- certification_type
- professional
- free
- course_feature
- topic
- ocw_topic
- level
- department
- platform
- offered_by
- delivery
- title
- url
- resource_type_group
- resource_category
- published
type: string
description: |-
* `readable_id` - Readable Id
* `resource_type` - Resource Type
* `certification` - Certification
* `certification_type` - Certification Type
* `professional` - Professional
* `free` - Free
* `course_feature` - Course Feature
* `topic` - Topic
* `ocw_topic` - Ocw Topic
* `level` - Level
* `department` - Department
* `platform` - Platform
* `offered_by` - Offered By
* `delivery` - Delivery
* `title` - Title
* `url` - Url
* `resource_type_group` - Resource Type Group
* `resource_category` - Resource Category
* `published` - Published
description: "aggregations for facet counts \n\n* `readable_id`\
\ - Readable Id\n* `resource_type` - Resource Type\n* `certification` -\
\ Certification\n* `certification_type` - Certification Type\n* `professional`\
\ - Professional\n* `free` - Free\n* `course_feature` - Course Feature\n\
* `topic` - Topic\n* `ocw_topic` - Ocw Topic\n* `level` - Level\n* `department`\
\ - Department\n* `platform` - Platform\n* `offered_by` - Offered By\n*\
\ `delivery` - Delivery\n* `title` - Title\n* `url` - Url\n* `resource_type_group`\
\ - Resource Type Group\n* `resource_category` - Resource Category\n* `published`\
\ - Published"
- in: query
name: certification
schema:
Expand Down Expand Up @@ -1255,6 +1362,13 @@ paths:
schema:
type: boolean
nullable: true
- in: query
name: published
schema:
type: boolean
default: true
description: If the resource is published. We default to True unless passed
in
- in: query
name: q
schema:
Expand Down
14 changes: 14 additions & 0 deletions vector_search/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
"title": "title",
"url": "url",
"resource_type_group": "resource_type_group",
"resource_category": "resource_category",
"published": "published",
}


Expand All @@ -71,6 +73,7 @@
"url": models.PayloadSchemaType.KEYWORD,
"title": models.PayloadSchemaType.KEYWORD,
"resource_type_group": models.PayloadSchemaType.KEYWORD,
"resource_category": models.PayloadSchemaType.KEYWORD,
}

"""
Expand All @@ -92,3 +95,14 @@
QDRANT_TOPIC_INDEXES = {
"name": models.PayloadSchemaType.KEYWORD,
}


CONTENT_FILES_RETRIEVE_PAYLOAD = True
RESOURCES_RETRIEVE_PAYLOAD = ["readable_id"]


COLLECTION_PARAM_MAP = {
RESOURCES_COLLECTION_NAME: QDRANT_RESOURCE_PARAM_MAP,
TOPICS_COLLECTION_NAME: QDRANT_TOPICS_PARAM_MAP,
CONTENT_FILES_COLLECTION_NAME: QDRANT_CONTENT_FILE_PARAM_MAP,
}
Loading
Loading