Browse Source

fetch vector search result from api

tumao 4 years ago
parent
commit
bba1494f27

+ 6 - 0
client/src/http/BaseModel.ts

@@ -78,4 +78,10 @@ export default class BaseModel {
     const res = await http.post(path, data);
     return res.data;
   }
+
+  static async vectorSearch(options: updateParamsType) {
+    const { path, data } = options;
+    const res = await http.post(path, data);
+    return res.data.data;
+  }
 }

+ 8 - 0
client/src/http/Collection.ts

@@ -1,6 +1,7 @@
 import { ChildrenStatusType, StatusEnum } from '../components/status/Types';
 import { CollectionView, InsertDataParam } from '../pages/collections/Types';
 import { Field } from '../pages/schema/Types';
+import { VectorSearchParam } from '../pages/seach/Types';
 import { IndexState, ShowCollectionsType } from '../types/Milvus';
 import { formatNumber } from '../utils/Common';
 import BaseModel from './BaseModel';
@@ -80,6 +81,13 @@ export class CollectionHttp extends BaseModel implements CollectionView {
     });
   }
 
+  static vectorSearchData(collectionName: string, params: VectorSearchParam) {
+    return super.vectorSearch({
+      path: `${this.COLLECTIONS_URL}/${collectionName}/search`,
+      data: params,
+    });
+  }
+
   get _autoId() {
     return this.autoID;
   }

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

@@ -2,7 +2,7 @@ const searchTrans = {
   firstTip: '1. Enter vector value',
   secondTip: '2. Choose collection and field',
   thirdTip: '3. Set search parameters',
-  vectorPlaceholder: 'Please input your vector value here',
+  vectorPlaceholder: 'Please input your vector value here, e.g. [1, 2, 3, 4]',
   collection: 'Choose Collection',
   field: 'Choose Field',
   empty: 'start your vector search',

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

@@ -2,7 +2,7 @@ const searchTrans = {
   firstTip: '1. Enter vector value',
   secondTip: '2. Choose collection and field',
   thirdTip: '3. Set search parameters',
-  vectorPlaceholder: 'Please input your vector value here',
+  vectorPlaceholder: 'Please input your vector value here, e.g. [1, 2, 3, 4]',
   collection: 'Choose Collection',
   field: 'Choose Field',
   empty: 'start your vector search',

+ 17 - 3
client/src/pages/seach/Types.ts

@@ -1,6 +1,6 @@
 import { Option } from '../../components/customSelector/Types';
 import { EmbeddingTypeEnum, searchKeywordsType } from '../../consts/Milvus';
-import { DataType } from '../collections/Types';
+import { DataType, DataTypeEnum } from '../collections/Types';
 import { IndexView } from '../schema/Types';
 
 export interface SearchParamsProps {
@@ -25,8 +25,8 @@ export interface SearchParamsProps {
 export interface SearchResultView {
   // dynamic field names
   [key: string]: string | number;
-  _rank: number;
-  _distance: number;
+  rank: number;
+  distance: number;
 }
 
 export interface FieldOption extends Option {
@@ -46,3 +46,17 @@ export interface SearchParamInputConfig {
   value: number | string;
   handleChange: (value: number) => void;
 }
+
+export interface VectorSearchParam {
+  expr?: string;
+  search_params: { key: string; value: string | number }[];
+  vectors: any;
+  output_fields: string[];
+  vector_type: number | DataTypeEnum;
+}
+
+export interface SearchResult {
+  // dynamic field names
+  [key: string]: string | number;
+  score: number;
+}

+ 80 - 12
client/src/pages/seach/VectorSearch.tsx

@@ -9,7 +9,7 @@ import {
   DEFAULT_METRIC_VALUE_MAP,
   EmbeddingTypeEnum,
 } from '../../consts/Milvus';
-import { FieldOption, SearchResultView } from './Types';
+import { FieldOption, SearchResultView, VectorSearchParam } from './Types';
 import MilvusGrid from '../../components/grid/Grid';
 import EmptyCard from '../../components/cards/EmptyCard';
 import icons from '../../components/icons/Icons';
@@ -19,9 +19,12 @@ import SimpleMenu from '../../components/menu/SimpleMenu';
 import { TOP_K_OPTIONS } from './Constants';
 import { Option } from '../../components/customSelector/Types';
 import { CollectionHttp } from '../../http/Collection';
-import { CollectionData, DataType } from '../collections/Types';
+import { CollectionData, DataType, DataTypeEnum } from '../collections/Types';
 import { IndexHttp } from '../../http/Index';
 import { getVectorSearchStyles } from './Styles';
+import { parseValue } from '../../utils/Insert';
+import { transferSearchResult } from '../../utils/search';
+import { ColDefinitionsType } from '../../components/grid/Types';
 
 const VectorSearch = () => {
   useNavigationHook(ALL_ROUTER_TYPES.SEARCH);
@@ -30,7 +33,6 @@ const VectorSearch = () => {
   const classes = getVectorSearchStyles();
 
   // data stored inside the component
-  // TODO: set real loading
   const [tableLoading, setTableLoading] = useState<boolean>(false);
   const [collections, setCollections] = useState<CollectionData[]>([]);
   const [selectedCollection, setSelectedCollection] = useState<string>('');
@@ -68,10 +70,29 @@ const VectorSearch = () => {
   const outputFields: string[] = useMemo(() => {
     const fields =
       collections.find(c => c._name === selectedCollection)?._fields || [];
-    return fields.map(f => f._fieldName);
+    // vector field can't be output fields
+    const invalidTypes = ['BinaryVector', 'FloatVector'];
+    const nonVectorFields = fields.filter(
+      field => !invalidTypes.includes(field._fieldType)
+    );
+    return nonVectorFields.map(f => f._fieldName);
   }, [selectedCollection, collections]);
 
-  const { metricType, indexType, indexParams } = useMemo(() => {
+  const colDefinitions: ColDefinitionsType[] = useMemo(() => {
+    // filter id and score
+    return searchResult.length > 0
+      ? Object.keys(searchResult[0])
+          .filter(item => item !== 'id' && item !== 'score')
+          .map(key => ({
+            id: key,
+            align: 'left',
+            disablePadding: false,
+            label: key,
+          }))
+      : [];
+  }, [searchResult]);
+
+  const { metricType, indexType, indexParams, fieldType } = useMemo(() => {
     if (selectedField !== '') {
       // field options must contain selected field, so selectedFieldInfo will never undefined
       const selectedFieldInfo = fieldOptions.find(
@@ -94,10 +115,16 @@ const VectorSearch = () => {
         metricType: metric,
         indexType: index!._indexType,
         indexParams,
+        fieldType: DataTypeEnum[selectedFieldInfo?.fieldType!],
       };
     }
 
-    return { metricType: '', indexType: '', indexParams: [] };
+    return {
+      metricType: '',
+      indexType: '',
+      indexParams: [],
+      fieldType: 0,
+    };
   }, [selectedField, fieldOptions]);
 
   // fetch data
@@ -154,7 +181,45 @@ const VectorSearch = () => {
     handleCurrentPage(page);
   };
   const handleReset = () => {};
-  const handleSearch = () => {};
+  const handleSearch = async () => {
+    const searhParamPairs = [
+      // dynamic search params
+      {
+        key: 'params',
+        value: JSON.stringify(searchParam),
+      },
+      {
+        key: 'anns_field',
+        value: selectedField,
+      },
+      {
+        key: 'topk',
+        value: topK,
+      },
+      {
+        key: 'metric_type',
+        value: metricType,
+      },
+    ];
+
+    const params: VectorSearchParam = {
+      output_fields: outputFields,
+      expr: '',
+      search_params: searhParamPairs,
+      vectors: [parseValue(vectors)],
+      vector_type: fieldType,
+    };
+
+    setTableLoading(true);
+    const res = await CollectionHttp.vectorSearchData(
+      selectedCollection,
+      params
+    );
+    setTableLoading(false);
+
+    const result = transferSearchResult(res.results);
+    setSearchResult(result);
+  };
   const handleFilter = () => {};
   const handleClearFilter = () => {};
   const handleVectorChange = (value: string) => {
@@ -196,7 +261,7 @@ const VectorSearch = () => {
             onChange={(e: { target: { value: unknown } }) => {
               const collection = e.target.value;
               setSelectedCollection(collection as string);
-              // everytime selected collection changed, reset field
+              // every time selected collection changed, reset field
               setSelectedField('');
             }}
           />
@@ -210,7 +275,6 @@ const VectorSearch = () => {
             onChange={(e: { target: { value: unknown } }) => {
               const field = e.target.value;
               setSelectedField(field as string);
-              console.log('selected field', field);
             }}
           />
         </fieldset>
@@ -245,7 +309,11 @@ const VectorSearch = () => {
             label={searchTrans('topK', { number: topK })}
             menuItems={TOP_K_OPTIONS.map(item => ({
               label: item.toString(),
-              callback: () => setTopK(item),
+              callback: () => {
+                setTopK(item);
+                // TODO: check search validation before search
+                // handleSearch();
+              },
               wrapperClass: classes.menuItem,
             }))}
             buttonProps={{
@@ -288,10 +356,10 @@ const VectorSearch = () => {
       {searchResult.length > 0 || tableLoading ? (
         <MilvusGrid
           toolbarConfigs={[]}
-          colDefinitions={[]}
+          colDefinitions={colDefinitions}
           rows={result}
           rowCount={total}
-          primaryKey="_row"
+          primaryKey="rank"
           page={currentPage}
           onChangePage={handlePageChange}
           rowsPerPage={pageSize}

+ 18 - 7
client/src/utils/Format.ts

@@ -74,18 +74,29 @@ export const getEnumKeyByValue = (enumObj: any, enumValue: any) => {
   return '--';
 };
 
+/**
+ *
+ * @param obj e.g. {name: 'test'}
+ * @returns key value pair, e.g. [{key: 'name', value: 'test'}]
+ */
+export const getKeyValuePairFromObj = (
+  obj: { [key in string]: any }
+): { key: string; value: any }[] => {
+  const pairs: { key: string; value: string }[] = Object.entries(obj).map(
+    ([key, value]) => ({
+      key,
+      value: value as string,
+    })
+  );
+  return pairs;
+};
+
 export const getKeyValueListFromJsonString = (
   json: string
 ): { key: string; value: string }[] => {
   try {
     const obj = JSON.parse(json);
-
-    const pairs: { key: string; value: string }[] = Object.entries(obj).map(
-      ([key, value]) => ({
-        key,
-        value: value as string,
-      })
-    );
+    const pairs = getKeyValuePairFromObj(obj);
 
     return pairs;
   } catch (err) {

+ 1 - 1
client/src/utils/Insert.ts

@@ -26,7 +26,7 @@ const replaceKeysByIndex = (obj: any, newKeys: string[]) => {
   return Object.assign({}, ...keyValues);
 };
 
-const parseValue = (value: string) => {
+export const parseValue = (value: string) => {
   try {
     return JSON.parse(value);
   } catch (err) {

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

@@ -0,0 +1,15 @@
+import { SearchResult, SearchResultView } from '../pages/seach/Types';
+
+export const transferSearchResult = (
+  result: SearchResult[]
+): SearchResultView[] => {
+  const resultView = result
+    .sort((a, b) => a.score - b.score)
+    .map((r, index) => ({
+      rank: index + 1,
+      distance: r.score,
+      ...r,
+    }));
+
+  return resultView;
+};