Browse Source

support group by for milvus 2.4 (#557)

* support group by

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* remove unused imports

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

---------

Signed-off-by: ryjiang <jiangruiyi@gmail.com>
ryjiang 1 year ago
parent
commit
90947785b1

+ 1 - 0
client/src/i18n/cn/search.ts

@@ -24,6 +24,7 @@ const searchTrans = {
   noVectorToSearch: '没有用于搜索的向量数据.',
   noVectorToSearch: '没有用于搜索的向量数据.',
   noSelectedVectorField: '至少选择一个向量字段进行搜索.',
   noSelectedVectorField: '至少选择一个向量字段进行搜索.',
   rerank: '排序器',
   rerank: '排序器',
+  groupBy: '分组',
 };
 };
 
 
 export default searchTrans;
 export default searchTrans;

+ 1 - 0
client/src/i18n/en/search.ts

@@ -24,6 +24,7 @@ const searchTrans = {
   noVectorToSearch: 'No vector data to search.',
   noVectorToSearch: 'No vector data to search.',
   noSelectedVectorField: 'At least select one vector field to search.',
   noSelectedVectorField: 'At least select one vector field to search.',
   rerank: 'Reranker',
   rerank: 'Reranker',
+  groupBy: 'Group By',
 };
 };
 
 
 export default searchTrans;
 export default searchTrans;

+ 1 - 0
client/src/pages/databases/collections/search/Search.tsx

@@ -410,6 +410,7 @@ const Search = (props: CollectionDataProps) => {
               onSlideChangeCommitted={() => {
               onSlideChangeCommitted={() => {
                 setHighlightField('');
                 setHighlightField('');
               }}
               }}
+              fields={searchParams.collection.schema.scalarFields}
               searchParams={searchParams}
               searchParams={searchParams}
               searchGlobalParams={searchParams.globalParams}
               searchGlobalParams={searchParams.globalParams}
               handleFormChange={(params: any) => {
               handleFormChange={(params: any) => {

+ 34 - 10
client/src/pages/databases/collections/search/SearchGlobalParams.tsx

@@ -7,18 +7,27 @@ import {
   CONSISTENCY_LEVEL_OPTIONS,
   CONSISTENCY_LEVEL_OPTIONS,
   TOP_K_OPTIONS,
   TOP_K_OPTIONS,
   RERANKER_OPTIONS,
   RERANKER_OPTIONS,
+  DataTypeStringEnum,
 } from '@/consts';
 } from '@/consts';
 import { SearchParams, GlobalParams } from '../../types';
 import { SearchParams, GlobalParams } from '../../types';
+import { FieldObject } from '@server/types';
 
 
-export interface CollectionDataProps {
+export interface SearchGlobalProps {
   searchGlobalParams: GlobalParams;
   searchGlobalParams: GlobalParams;
   searchParams: SearchParams;
   searchParams: SearchParams;
   handleFormChange: (form: GlobalParams) => void;
   handleFormChange: (form: GlobalParams) => void;
   onSlideChange: (field: string) => void;
   onSlideChange: (field: string) => void;
   onSlideChangeCommitted: () => void;
   onSlideChangeCommitted: () => void;
+  fields: FieldObject[];
 }
 }
 
 
-const SearchGlobalParams = (props: CollectionDataProps) => {
+const UNSPORTED_GROUPBY_TYPES = [
+  DataTypeStringEnum.Double,
+  DataTypeStringEnum.Float,
+  DataTypeStringEnum.JSON,
+];
+
+const SearchGlobalParams = (props: SearchGlobalProps) => {
   // props
   // props
   const {
   const {
     searchParams,
     searchParams,
@@ -26,6 +35,7 @@ const SearchGlobalParams = (props: CollectionDataProps) => {
     handleFormChange,
     handleFormChange,
     onSlideChange,
     onSlideChange,
     onSlideChangeCommitted,
     onSlideChangeCommitted,
+    fields,
   } = props;
   } = props;
   const selectedCount = searchParams.searchParams.filter(
   const selectedCount = searchParams.searchParams.filter(
     sp => sp.selected
     sp => sp.selected
@@ -52,14 +62,14 @@ const SearchGlobalParams = (props: CollectionDataProps) => {
     [handleFormChange, searchGlobalParams]
     [handleFormChange, searchGlobalParams]
   );
   );
 
 
-  const onRerankChanged = useCallback(
-    (e: { target: { value: unknown } }) => {
-      const rerankerStr = e.target.value as 'rrf' | 'weighted';
-
-      handleInputChange('rerank', rerankerStr);
-    },
-    [selectedCount, handleInputChange]
-  );
+  const groupByOptions = fields
+    .filter(f => !UNSPORTED_GROUPBY_TYPES.includes(f.data_type as any))
+    .map(f => {
+      return {
+        value: f.name,
+        label: f.name,
+      };
+    });
 
 
   return (
   return (
     <>
     <>
@@ -86,6 +96,20 @@ const SearchGlobalParams = (props: CollectionDataProps) => {
         }}
         }}
       />
       />
 
 
+      {!showReranker && (
+        <CustomSelector
+          options={[{ label: '--', value: '' }, ...groupByOptions]}
+          value={searchGlobalParams.group_by_field || ''}
+          label={searchTrans('groupBy')}
+          wrapperClass="selector"
+          variant="filled"
+          onChange={(e: { target: { value: unknown } }) => {
+            const groupBy = e.target.value as string;
+            handleInputChange('group_by_field', groupBy);
+          }}
+        />
+      )}
+
       {showReranker && (
       {showReranker && (
         <>
         <>
           <CustomSelector
           <CustomSelector

+ 1 - 0
client/src/pages/databases/types.ts

@@ -17,6 +17,7 @@ export type GlobalParams = {
   rrfParams: { k: number };
   rrfParams: { k: number };
   weightedParams: { weights: number[] };
   weightedParams: { weights: number[] };
   round_decimal?: number;
   round_decimal?: number;
+  group_by_field?: string;
 };
 };
 
 
 export type SearchResultView = {
 export type SearchResultView = {

+ 5 - 0
client/src/utils/search.ts

@@ -46,6 +46,11 @@ export const buildSearchParams = (
     consistency_level: searchParams.globalParams.consistency_level,
     consistency_level: searchParams.globalParams.consistency_level,
   };
   };
 
 
+  // group_by_field if exists
+  if (searchParams.globalParams.group_by_field) {
+    params.group_by_field = searchParams.globalParams.group_by_field;
+  }
+
   // reranker if exists
   // reranker if exists
   if (data.length > 1) {
   if (data.length > 1) {
     if (searchParams.globalParams.rerank === 'rrf') {
     if (searchParams.globalParams.rerank === 'rrf') {

+ 2 - 1
server/src/collections/collections.service.ts

@@ -261,7 +261,7 @@ export class CollectionsService {
     const searchParams = data as HybridSearchReq;
     const searchParams = data as HybridSearchReq;
     const isHybrid =
     const isHybrid =
       Array.isArray(searchParams.data) && searchParams.data.length > 1;
       Array.isArray(searchParams.data) && searchParams.data.length > 1;
-    const singleSearchParams = cloneObj(data) as SearchSimpleReq;
+    let singleSearchParams = cloneObj(data) as SearchSimpleReq;
 
 
     // for 2.3.x milvus
     // for 2.3.x milvus
     if (searchParams.data && searchParams.data.length === 1) {
     if (searchParams.data && searchParams.data.length === 1) {
@@ -273,6 +273,7 @@ export class CollectionsService {
       }
       }
       singleSearchParams.data = searchParams.data[0].data;
       singleSearchParams.data = searchParams.data[0].data;
       singleSearchParams.anns_field = searchParams.data[0].anns_field;
       singleSearchParams.anns_field = searchParams.data[0].anns_field;
+      singleSearchParams.group_by_field = searchParams.group_by_field;
     }
     }
 
 
     const res = await milvusClient.search(
     const res = await milvusClient.search(