chenpei d0dc94e36d feat: 将弹幕功能替换为AI聊天助手
新功能:
- 创建AI聊天助手组件(AIChatWidget)
- 支持文字+图片多模态对话
- 集成Gemini AI API
- 支持图片编辑和内容生成
- 实时对话历史管理
- 优雅的UI设计,支持黑夜模式

技术实现:
- 后端API代理Gemini API(/api/ai-chat)
- 支持最多5张图片上传
- 流式响应处理
- 对话历史管理
- Base64图片处理

配置:
- 在.env中添加Gemini API配置
- GEMINI_API_KEY
- GEMINI_API_BASE_URL
- GEMINI_MODEL

替换原弹幕功能,提供更强大的AI助手体验!
2025-10-27 17:45:37 +08:00

856 lines
43 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# coding:utf-8
import json
import warnings
from typing import Union, List, Dict
from volcengine.viking_db.common import (RetryOption, Data, VectorOrder, ScalarOrder,
AggResult, IndexSortResult, SortResultItem)
class Index(object):
def __init__(self, collection_name, index_name, vector_index, scalar_index, stat, viking_db_service, description="",
cpu_quota=2, partition_by=None, create_time=None, update_time=None, update_person=None,
index_cost=None, shard_count=None, shard_policy=None, retry_option=None):
"""
:param collection_name: the name of VikingDB collection.
:type collection_name: str
:param index_name: the name of VikingDB index.
:type index_name: str
:param vector_index: ANN vector index information.
:type vector_index: dict
:param scalar_index: scalar index information.
:type scalar_index: set
:param stat: index status like 'READY'.
:type stat: str
:param viking_db_service: VikingDBService instance.
:type viking_db_service: VikingDBService
:param description: the description of index.
:type description: str
:param cpu_quota: allocated cpu quota of the index.
:type cpu_quota: int
:param partition_by: the name of sub-index, if not specified, the sub-index is single default.
:type partition_by: str
:param shard_count: the number of shards, by default it's 1.
:type shard_count: int
:param shard_policy: the sharding policy of the index, by default it's 'auto'.
:type shard_policy: str
:param retry_option: retry option.
:type retry_option: RetryOption
"""
self.collection_name = collection_name
self.index_name = index_name
self.description = description
self.vector_index = vector_index
self.scalar_index = scalar_index
self.stat = stat
self.viking_db_service = viking_db_service
self.cpu_quota = cpu_quota
self.partition_by = partition_by
self.create_time = create_time
self.update_time = update_time
self.update_person = update_person
self.index_cost = index_cost
self.shard_count = shard_count
self.shard_policy = shard_policy
self.retry_option = retry_option if retry_option else RetryOption()
# 获取primary_key
col = self.viking_db_service.get_exception("GetCollection", {"collection_name": self.collection_name})
col = json.loads(col)
self.primary_key = col["data"]["primary_key"]
def search(self, order=None, filter=None, limit=10, output_fields=None, partition="default", dense_weight=None,
primary_key_in=None, primary_key_not_in=None, post_process_ops=None, post_process_input_limit=None,
retry=True):
"""
Search for vectors or scalars similar to a given vector or scalar.
:param order: the given vector or scalar or None.
:type order: VectorOrder or ScalarOrder or None
:param filter: filter conditions.
:type filter: dict
:param limit: number of retrieved results.
:type limit: int
:param output_fields: specify the list of scalar fields to be returned.
:type output_fields: list
:param partition: the name of sub-index.
:type partition: int or str or list[int] or list[str]
:type: list
:param dense_weight: the weight of dense vector, the value should be a float in range [0.2, 1.0].
:type dense_weight: float
:type primary_key_in: filter data by primary key value, list[int] or list[str]
:type: list
:type primary_key_not_in: filter out data by primary key value, list[int] or list[str]
:type: list
:type post_process_ops: post process operators
:type: list[dict]
:type post_process_input_limit: number of data input to post process operators
:type: int
:param retry: whether to retry when the request fails caused by 1000029: QuotaLimiterException.
:type retry: bool
"""
if isinstance(order, VectorOrder):
res = []
if order.vector is not None:
res = self.search_by_vector(order.vector, sparse_vectors=order.sparse_vectors, filter=filter, limit=limit,
output_fields=output_fields, partition=partition, dense_weight=dense_weight,
primary_key_in=None, primary_key_not_in=None, post_process_ops=None, post_process_input_limit=None,
retry=retry)
elif order.id is not None:
res = self.search_by_id(order.id, filter=filter, limit=limit,
output_fields=output_fields, partition=partition, dense_weight=dense_weight,
primary_key_in=None, primary_key_not_in=None, post_process_ops=None, post_process_input_limit=None,
retry=retry)
return res
elif isinstance(order, ScalarOrder):
search = {}
order_by_scalar = {"order": order.order.value, "field_name": order.field_name}
search = {"order_by_scalar": order_by_scalar, "limit": limit, "partition": partition}
if output_fields is not None:
search["output_fields"] = output_fields
if filter is not None:
search['filter'] = filter
if primary_key_in is not None:
search['primary_key_in'] = primary_key_in
if primary_key_not_in is not None:
search['primary_key_not_in'] = primary_key_not_in
if post_process_ops is not None:
search['post_process_ops'] = post_process_ops
if post_process_input_limit is not None:
search['post_process_input_limit'] = post_process_input_limit
params = {"collection_name": self.collection_name, "index_name": self.index_name, "search": search}
# print(params)
remaining = self.retry_option.new_remaining(retry)
res = self.viking_db_service._retry_request("SearchIndex", {}, json.dumps(params), remaining, self.retry_option)
res = json.loads(res)
datas = []
# 返回数据是个列表,每个id又对应一个列表,但是这里输入id好像只能传一个值,所以要for两次
for items in res["data"]:
for item in items:
id = item[self.primary_key]
fields = {}
if output_fields != [] or output_fields is None:
fields = item["fields"]
data = Data(fields, id=id, timestamp=None, score=item["score"], dist=item.get('dist', None))
datas.append(data)
return datas
elif order is None:
search = {"limit": limit, "partition": partition}
if output_fields is not None:
search["output_fields"] = output_fields
if filter is not None:
search['filter'] = filter
if primary_key_in is not None:
search['primary_key_in'] = primary_key_in
if primary_key_not_in is not None:
search['primary_key_not_in'] = primary_key_not_in
if post_process_ops is not None:
search['post_process_ops'] = post_process_ops
if post_process_input_limit is not None:
search['post_process_input_limit'] = post_process_input_limit
params = {"collection_name": self.collection_name, "index_name": self.index_name, "search": search}
remaining = self.retry_option.new_remaining(retry)
res = self.viking_db_service._retry_request("SearchIndex", {}, json.dumps(params), remaining, self.retry_option)
res = json.loads(res)
datas = []
for items in res["data"]:
for item in items:
id = item[self.primary_key]
fields = {}
if output_fields != [] or output_fields is None:
fields = item["fields"]
data = Data(fields, id=id, timestamp=None, score=item["score"], dist=item.get('dist', None))
datas.append(data)
return datas
async def async_search(self, order=None, filter=None, limit=10, output_fields=None, partition="default",
dense_weight=None, primary_key_in=None, primary_key_not_in=None, post_process_ops=None, post_process_input_limit=None):
if isinstance(order, VectorOrder):
res = []
if order.vector is not None:
res = await self.async_search_by_vector(order.vector, sparse_vectors=order.sparse_vectors, filter=filter, limit=limit,
output_fields=output_fields, partition=partition, dense_weight=dense_weight,
primary_key_in=None, primary_key_not_in=None,
post_process_ops=None, post_process_input_limit=None)
elif order.id is not None:
res = await self.async_search_by_id(order.id, filter=filter, limit=limit, output_fields=output_fields,
partition=partition, dense_weight=dense_weight,
primary_key_in=None, primary_key_not_in=None,
post_process_ops=None, post_process_input_limit=None)
return res
elif isinstance(order, ScalarOrder):
search = {}
order_by_scalar = {"order": order.order.value, "field_name": order.field_name}
search = {"order_by_scalar": order_by_scalar, "limit": limit, "partition": partition}
if output_fields is not None:
search["output_fields"] = output_fields
if filter is not None:
search['filter'] = filter
if primary_key_in is not None:
search['primary_key_in'] = primary_key_in
if primary_key_not_in is not None:
search['primary_key_not_in'] = primary_key_not_in
if post_process_ops is not None:
search['post_process_ops'] = post_process_ops
if post_process_input_limit is not None:
search['post_process_input_limit'] = post_process_input_limit
params = {"collection_name": self.collection_name, "index_name": self.index_name, "search": search}
res = await self.viking_db_service.async_json_exception("SearchIndex", {}, json.dumps(params))
res = json.loads(res)
datas = []
# 返回数据是个列表,每个id又对应一个列表,但是这里输入id好像只能传一个值,所以要for两次
for items in res["data"]:
for item in items:
id = item[self.primary_key]
fields = {}
if output_fields != [] or output_fields is None:
fields = item["fields"]
data = Data(fields, id=id, timestamp=None, score=item["score"], dist=item.get('dist', None))
datas.append(data)
return datas
elif order is None:
search = {"limit": limit, "partition": partition}
if output_fields is not None:
search["output_fields"] = output_fields
if filter is not None:
search['filter'] = filter
if primary_key_in is not None:
search['primary_key_in'] = primary_key_in
if primary_key_not_in is not None:
search['primary_key_not_in'] = primary_key_not_in
if post_process_ops is not None:
search['post_process_ops'] = post_process_ops
if post_process_input_limit is not None:
search['post_process_input_limit'] = post_process_input_limit
params = {"collection_name": self.collection_name, "index_name": self.index_name, "search": search}
res = await self.viking_db_service.async_json_exception("SearchIndex", {}, json.dumps(params))
res = json.loads(res)
datas = []
for items in res["data"]:
for item in items:
id = item[self.primary_key]
fields = {}
if output_fields != [] or output_fields is None:
fields = item["fields"]
data = Data(fields, id=id, timestamp=None, score=item["score"], dist=item.get('dist', None))
datas.append(data)
return datas
def search_by_id(self, id, filter=None, limit=10, output_fields=None, partition="default", dense_weight=None,
primary_key_in=None, primary_key_not_in=None, post_process_ops=None, post_process_input_limit=None,
retry=True, scale_k=0):
"""
Search for vectors similar to a given vector based on its id.
:param id: the primary key.
:type id: str
:param filter: filter conditions.
:type filter: dict
:param limit: number of retrieved results.
:type limit: int
:param output_fields: specify the list of scalar fields to be returned.
:type output_fields: list
:param partition: the name of sub-index.
:type partition: int or str or list[int] or list[str]
:rtype: list
:param dense_weight: the weight of dense vector, the value should be a float in range [0.2, 1.0].
:type dense_weight: float
:type primary_key_in: filter data by primary key value, list[int] or list[str]
:type: list
:type primary_key_not_in: filter out data by primary key value, list[int] or list[str]
:type: list
:type post_process_ops: post process operators
:type: list[dict]
:type post_process_input_limit: number of data input to post process operators
:type: int
:param retry: whether to retry when the request fails caused by 1000029: QuotaLimiterException.
:type retry: bool
"""
search = {}
order_by_id = {"primary_keys": id}
search = {"order_by_vector": order_by_id, "limit": limit, "partition": partition}
if output_fields is not None:
search["output_fields"] = output_fields
if filter is not None:
search['filter'] = filter
if dense_weight is not None:
search['dense_weight'] = dense_weight
if primary_key_in is not None:
search['primary_key_in'] = primary_key_in
if primary_key_not_in is not None:
search['primary_key_not_in'] = primary_key_not_in
if post_process_ops is not None:
search['post_process_ops'] = post_process_ops
if post_process_input_limit is not None:
search['post_process_input_limit'] = post_process_input_limit
if scale_k > 0:
search['scale_k'] = scale_k
params = {"collection_name": self.collection_name, "index_name": self.index_name, "search": search}
# print(params)
remaining = self.retry_option.new_remaining(retry)
res = self.viking_db_service._retry_request("SearchIndex", {}, json.dumps(params), remaining, self.retry_option)
res = json.loads(res)
# print(res["data"])
datas = []
# 返回数据是个列表,每个id又对应一个列表,但是这里输入id好像只能传一个值,所以要for两次
for items in res["data"]:
for item in items:
# print(item)
id = item[self.primary_key]
# print(id)
fields = {}
if output_fields != [] or output_fields is None:
fields = item["fields"]
# print(id, fields)
data = Data(fields, id=id, timestamp=None, score=item["score"], dist=item.get('dist', None))
datas.append(data)
# print("==================")
return datas
async def async_search_by_id(self, id, filter=None, limit=10, output_fields=None, partition="default", dense_weight=None,
primary_key_in=None, primary_key_not_in=None, post_process_ops=None, post_process_input_limit=None):
search = {}
order_by_id = {"primary_keys": id}
search = {"order_by_vector": order_by_id, "limit": limit, "partition": partition}
if output_fields is not None:
search["output_fields"] = output_fields
if filter is not None:
search['filter'] = filter
if dense_weight is not None:
search['dense_weight'] = dense_weight
if primary_key_in is not None:
search['primary_key_in'] = primary_key_in
if primary_key_not_in is not None:
search['primary_key_not_in'] = primary_key_not_in
if post_process_ops is not None:
search['post_process_ops'] = post_process_ops
if post_process_input_limit is not None:
search['post_process_input_limit'] = post_process_input_limit
params = {"collection_name": self.collection_name, "index_name": self.index_name, "search": search}
# print(params)
res = await self.viking_db_service.async_json_exception("SearchIndex", {}, json.dumps(params))
res = json.loads(res)
# print(res["data"])
datas = []
# 返回数据是个列表,每个id又对应一个列表,但是这里输入id好像只能传一个值,所以要for两次
for items in res["data"]:
for item in items:
# print(item)
id = item[self.primary_key]
# print(id)
fields = {}
if output_fields != [] or output_fields is None:
fields = item["fields"]
# print(id, fields)
data = Data(fields, id=id, timestamp=None, score=item["score"], dist=item.get('dist', None))
datas.append(data)
# print("==================")
return datas
def search_by_vector(self, vector, sparse_vectors=None, filter=None, limit=10, output_fields=None, partition="default", dense_weight=None,
primary_key_in=None, primary_key_not_in=None, post_process_ops=None, post_process_input_limit=None, retry=True, scale_k=0):
"""
Search for vectors similar to a given vector.
:param vector: the given vector.
:type vector: list
:param filter: filter conditions.
:type filter: dict
:param limit: number of retrieved results.
:type limit: int
:param output_fields: specify the list of scalar fields to be returned.
:type output_fields: list
:param partition: the name of sub-index.
:type partition: int or str or list[int] or list[str]
:rtype: list
:param dense_weight: the weight of dense vector, the value should be a float in range [0.2, 1.0].
:type dense_weight: float
:type primary_key_in: filter data by primary key value, list[int] or list[str]
:type: list
:type primary_key_not_in: filter out data by primary key value, list[int] or list[str]
:type: list
:type post_process_ops: post process operators
:type: list[dict]
:type post_process_input_limit: number of data input to post process operators
:type: int
:param retry: whether to retry when the request fails caused by 1000029: QuotaLimiterException.
:type retry: bool
"""
# vector是一个向量,不是list,但是数据库要求传入的是个列表
search = {}
order_by_vector = {"vectors": [vector]}
if sparse_vectors is not None:
order_by_vector['sparse_vectors'] = [sparse_vectors]
search = {"order_by_vector": order_by_vector, "limit": limit, "partition": partition}
if output_fields is not None:
search["output_fields"] = output_fields
if filter is not None:
search['filter'] = filter
if dense_weight is not None:
search['dense_weight'] = dense_weight
if primary_key_in is not None:
search['primary_key_in'] = primary_key_in
if primary_key_not_in is not None:
search['primary_key_not_in'] = primary_key_not_in
if post_process_ops is not None:
search['post_process_ops'] = post_process_ops
if post_process_input_limit is not None:
search['post_process_input_limit'] = post_process_input_limit
if scale_k > 0:
search['scale_k'] = scale_k
params = {"collection_name": self.collection_name, "index_name": self.index_name, "search": search}
remaining = self.retry_option.new_remaining(retry)
res = self.viking_db_service._retry_request("SearchIndex", {}, json.dumps(params), remaining, self.retry_option)
res = json.loads(res)
datas = []
# 返回数据是个列表,每个vector又对应一个列表,但是这里输入vector好像只能传一个值,所以要for两次
for items in res["data"]:
for item in items:
id = item[self.primary_key]
fields = {}
if output_fields != [] or output_fields is None:
fields = item["fields"]
data = Data(fields, id=id, timestamp=None, score=item["score"], dist=item.get('dist', None))
datas.append(data)
return datas
async def async_search_by_vector(self, vector, sparse_vectors=None, filter=None, limit=10, output_fields=None, partition="default", dense_weight=None,
primary_key_in=None, primary_key_not_in=None, post_process_ops=None, post_process_input_limit=None):
# vector是一个向量,不是list,但是数据库要求传入的是个列表
search = {}
order_by_vector = {"vectors": [vector]}
if sparse_vectors is not None:
order_by_vector['sparse_vectors'] = [sparse_vectors]
search = {"order_by_vector": order_by_vector, "limit": limit, "partition": partition}
if output_fields is not None:
search["output_fields"] = output_fields
if filter is not None:
search['filter'] = filter
if dense_weight is not None:
search['dense_weight'] = dense_weight
if primary_key_in is not None:
search['primary_key_in'] = primary_key_in
if primary_key_not_in is not None:
search['primary_key_not_in'] = primary_key_not_in
if post_process_ops is not None:
search['post_process_ops'] = post_process_ops
if post_process_input_limit is not None:
search['post_process_input_limit'] = post_process_input_limit
params = {"collection_name": self.collection_name, "index_name": self.index_name, "search": search}
res = await self.viking_db_service.async_json_exception("SearchIndex", {}, json.dumps(params))
res = json.loads(res)
datas = []
# 返回数据是个列表,每个vector又对应一个列表,但是这里输入vector好像只能传一个值,所以要for两次
for items in res["data"]:
for item in items:
id = item[self.primary_key]
fields = {}
if output_fields != [] or output_fields is None:
fields = item["fields"]
data = Data(fields, id=id, timestamp=None, score=item["score"], dist=item.get('dist', None))
datas.append(data)
return datas
def search_with_multi_modal(self, text=None, image=None, filter=None, limit=10, output_fields=None, partition="default", dense_weight=None, need_instruction=None,
primary_key_in=None, primary_key_not_in=None, post_process_ops=None, post_process_input_limit=None, retry=True, scale_k=0):
"""
Search with multi-modal data including type of text and image.
:param text: the given text.
:type text: str
:param image: the given image.
:type image: str
:param filter: filter conditions.
:type filter: dict
:param limit: number of retrieved results.
:type limit: int
:param output_fields: specify the list of scalar fields to be returned.
:type output_fields: list
:param partition: the name of sub-index.
:type partition: int or str or list[int] or list[str]
:rtype: list
:param dense_weight: the weight of dense vector, the value should be a float in range [0.2, 1.0].
:type dense_weight: float
:type need_instruction: whether need instruction for embedding
:type: bool
:type primary_key_in: filter data by primary key value, list[int] or list[str]
:type: list
:type primary_key_not_in: filter out data by primary key value, list[int] or list[str]
:type: list
:type post_process_ops: post process operators
:type: list[dict]
:type post_process_input_limit: number of data input to post process operators
:type: int
:param retry: whether to retry when the request fails caused by 1000029: QuotaLimiterException.
:type retry: bool
"""
if text is None and image is None:
raise Exception("not any modal data params exist")
order_by_raw = {}
if text is not None:
order_by_raw["text"] = text
if image is not None:
order_by_raw["image"] = image
search = {"order_by_raw": order_by_raw, "limit": limit, "partition": partition}
if output_fields is not None:
search["output_fields"] = output_fields
if filter is not None:
search['filter'] = filter
if dense_weight is not None:
search['dense_weight'] = dense_weight
if need_instruction is not None:
search['need_instruction'] = need_instruction
if primary_key_in is not None:
search['primary_key_in'] = primary_key_in
if primary_key_not_in is not None:
search['primary_key_not_in'] = primary_key_not_in
if post_process_ops is not None:
search['post_process_ops'] = post_process_ops
if post_process_input_limit is not None:
search['post_process_input_limit'] = post_process_input_limit
if scale_k > 0:
search['scale_k'] = scale_k
params = {"collection_name": self.collection_name, "index_name": self.index_name, "search": search}
remaining = self.retry_option.new_remaining(retry)
res = self.viking_db_service._retry_request("SearchIndex", {}, json.dumps(params), remaining, self.retry_option)
res = json.loads(res)
datas = []
# 返回数据是个列表,每个vector又对应一个列表,但是这里输入vector好像只能传一个值,所以要for两次
for items in res["data"]:
for item in items:
id = item[self.primary_key]
fields = {}
if output_fields != [] or output_fields is None:
fields = item["fields"]
text = None
if "text" in item:
text = item["text"]
data = Data(fields, id=id, timestamp=None, score=item["score"], text=text, dist=item.get('dist', None))
datas.append(data)
return datas
async def async_search_with_multi_modal(self, text=None, image=None, filter=None, limit=10, output_fields=None, partition="default", dense_weight=None, need_instruction=None,
primary_key_in=None, primary_key_not_in=None, post_process_ops=None, post_process_input_limit=None, scale_k=0):
if text is None and image is None:
raise Exception("not any modal data params exist")
order_by_raw = {}
if text is not None:
order_by_raw["text"] = text
if image is not None:
order_by_raw["image"] = image
search = {"order_by_raw": order_by_raw, "limit": limit, "partition": partition}
if output_fields is not None:
search["output_fields"] = output_fields
if filter is not None:
search['filter'] = filter
if dense_weight is not None:
search['dense_weight'] = dense_weight
if need_instruction is not None:
search['need_instruction'] = need_instruction
if primary_key_in is not None:
search['primary_key_in'] = primary_key_in
if primary_key_not_in is not None:
search['primary_key_not_in'] = primary_key_not_in
if post_process_ops is not None:
search['post_process_ops'] = post_process_ops
if post_process_input_limit is not None:
search['post_process_input_limit'] = post_process_input_limit
if scale_k > 0:
search['scale_k'] = scale_k
params = {"collection_name": self.collection_name, "index_name": self.index_name, "search": search}
res = await self.viking_db_service.async_json_exception("SearchIndex", {}, json.dumps(params))
res = json.loads(res)
datas = []
# 返回数据是个列表,每个vector又对应一个列表,但是这里输入vector好像只能传一个值,所以要for两次
for items in res["data"]:
for item in items:
id = item[self.primary_key]
fields = {}
if output_fields != [] or output_fields is None:
fields = item["fields"]
text = None
if "text" in item:
text = item["text"]
data = Data(fields, id=id, timestamp=None, score=item["score"], text=text, dist=item.get('dist', None))
datas.append(data)
return datas
def search_by_text(self, text, filter=None, limit=10, output_fields=None, partition="default", dense_weight=None, need_instruction=None,
primary_key_in=None, primary_key_not_in=None, post_process_ops=None, post_process_input_limit=None, retry=True, scale_k=0):
"""
Search for text similar to a given text. (You can use search_with_multi_modal instead.)
:param text: the given text.
:type text: Text
:param filter: filter conditions.
:type filter: dict
:param limit: number of retrieved results.
:type limit: int
:param output_fields: specify the list of scalar fields to be returned.
:type output_fields: list
:param partition: the name of sub-index.
:type partition: int or str or list[int] or list[str]
:rtype: list
:param dense_weight: the weight of dense vector, the value should be a float in range [0.2, 1.0].
:type dense_weight: float
:type need_instruction: whether need instruction for embedding
:type: bool
:type primary_key_in: filter data by primary key value, list[int] or list[str]
:type: list
:type primary_key_not_in: filter out data by primary key value, list[int] or list[str]
:type: list
:type post_process_ops: post process operators
:type: list[dict]
:type post_process_input_limit: number of data input to post process operators
:type: int
:param retry: whether to retry when the request fails caused by 1000029: QuotaLimiterException.
:type retry: bool
"""
warnings.warn("search_by_text is deprecated, please use search_with_multi_modal instead", DeprecationWarning)
order_by_raw = {"text": text.text}
search = {"order_by_raw": order_by_raw, "limit": limit, "partition": partition}
if output_fields is not None:
search["output_fields"] = output_fields
if filter is not None:
search['filter'] = filter
if dense_weight is not None:
search['dense_weight'] = dense_weight
if need_instruction is not None:
search['need_instruction'] = need_instruction
if primary_key_in is not None:
search['primary_key_in'] = primary_key_in
if primary_key_not_in is not None:
search['primary_key_not_in'] = primary_key_not_in
if post_process_ops is not None:
search['post_process_ops'] = post_process_ops
if post_process_input_limit is not None:
search['post_process_input_limit'] = post_process_input_limit
if scale_k > 0:
search['scale_k'] = scale_k
params = {"collection_name": self.collection_name, "index_name": self.index_name, "search": search}
remaining = self.retry_option.new_remaining(retry)
res = self.viking_db_service._retry_request("SearchIndex", {}, json.dumps(params), remaining, self.retry_option)
res = json.loads(res)
datas = []
# 返回数据是个列表,每个vector又对应一个列表,但是这里输入vector好像只能传一个值,所以要for两次
for items in res["data"]:
for item in items:
id = item[self.primary_key]
fields = {}
if output_fields != [] or output_fields is None:
fields = item["fields"]
text = None
if "text" in item:
text = item["text"]
data = Data(fields, id=id, timestamp=None, score=item["score"], text=text, dist=item.get('dist', None))
datas.append(data)
return datas
async def async_search_by_text(self, text, filter=None, limit=10, output_fields=None, partition="default", dense_weight=None, need_instruction=None,
primary_key_in=None, primary_key_not_in=None, post_process_ops=None, post_process_input_limit=None):
warnings.warn("async_search_by_text is deprecated, please use async_search_with_multi_modal instead", DeprecationWarning)
order_by_raw = {"text": text.text}
search = {"order_by_raw": order_by_raw, "limit": limit, "partition": partition}
if output_fields is not None:
search["output_fields"] = output_fields
if filter is not None:
search['filter'] = filter
if dense_weight is not None:
search['dense_weight'] = dense_weight
if need_instruction is not None:
search['need_instruction'] = need_instruction
if primary_key_in is not None:
search['primary_key_in'] = primary_key_in
if primary_key_not_in is not None:
search['primary_key_not_in'] = primary_key_not_in
if post_process_ops is not None:
search['post_process_ops'] = post_process_ops
if post_process_input_limit is not None:
search['post_process_input_limit'] = post_process_input_limit
params = {"collection_name": self.collection_name, "index_name": self.index_name, "search": search}
res = await self.viking_db_service.async_json_exception("SearchIndex", {}, json.dumps(params))
res = json.loads(res)
datas = []
# 返回数据是个列表,每个vector又对应一个列表,但是这里输入vector好像只能传一个值,所以要for两次
for items in res["data"]:
for item in items:
id = item[self.primary_key]
fields = {}
if output_fields != [] or output_fields is None:
fields = item["fields"]
text = None
if "text" in item:
text = item["text"]
data = Data(fields, id=id, timestamp=None, score=item["score"], text=text, dist=item.get('dist', None))
datas.append(data)
return datas
def search_agg(self, agg: Dict[str, any], filter=None, partition="default", retry=True) -> AggResult:
"""
Search and aggregate the data.
:param agg: the aggregation operator.
:type agg: dict
:param filter: filter conditions.
:type filter: dict
:param partition: the name of sub-index.
:type partition: int or str or list[int] or list[str]
:rtype: list
:param retry: whether to retry when the request fails caused by 1000029: QuotaLimiterException.
:type retry: bool
"""
params = {"collection_name": self.collection_name, "index_name": self.index_name,
"search": {"partition": partition}, "agg": agg}
if filter is not None:
params["search"]["filter"] = filter
remaining = self.retry_option.new_remaining(retry)
res = self.viking_db_service._retry_request("SearchAgg", {}, json.dumps(params), remaining, self.retry_option)
res = json.loads(res)
data = res["data"]
return AggResult(
agg_op=data["agg_op"],
group_by_field=data["group_by_field"],
agg_result=data["agg_result"],
)
async def async_search_agg(self, agg: Dict[str, any], filter=None, partition="default") -> AggResult:
params = {"collection_name": self.collection_name, "index_name": self.index_name,
"search": {"partition": partition}, "agg": agg}
if filter is not None:
params["search"]["filter"] = filter
res = await self.viking_db_service.async_json_exception("SearchAgg", {}, json.dumps(params))
res = json.loads(res)
data = res["data"]
return AggResult(
agg_op=data["agg_op"],
group_by_field=data["group_by_field"],
agg_result=data["agg_result"],
)
def sort(self, query_vector: List[float], primary_keys: List, retry=True):
"""
Index sort: input a query vector and primary key list, get the sorted score list.
:param query_vector: one query vector.
:type query_vector: list
:param primary_keys: primary key list.
:type primary_keys: list
:param retry: whether to retry when the request fails caused by 1000029: QuotaLimiterException.
:type retry: bool
"""
params = {"collection_name": self.collection_name, "index_name": self.index_name,
"sort": {"query_vector": query_vector, "primary_keys": primary_keys}}
remaining = self.retry_option.new_remaining(retry)
res = self.viking_db_service._retry_request("IndexSort", {}, json.dumps(params), remaining, self.retry_option)
res = json.loads(res)
data = res["data"]
result_items = []
for raw_item in data["sort_result"]:
primary_key = raw_item["primary_key"]
score = raw_item["score"]
result_items.append(SortResultItem(primary_key=primary_key, score=score))
return IndexSortResult(sort_result=result_items, primary_key_not_exist=data["primary_key_not_exist"])
def fetch_data(self, id: Union[str, List[str], int, List[int]], output_fields=None, partition="", retry=True):
"""
Fetch data by primary key (id).
this method is much faster than Collection.fetch_data() due to indexed.
:param text: the given primary key or keys.
:type text: str or int or list[str] or list[int]
:param output_fields: specify the list of scalar fields to be returned.
:type output_fields: list
:param partition: the name of sub-index.
:type partition: int or str or list[int] or list[str]
:param retry: whether to retry when the request fails caused by 1000029: QuotaLimiterException.
:type retry: bool
"""
params = {}
if isinstance(id, str) or isinstance(id, int):
params = {"collection_name": self.collection_name, "index_name": self.index_name,
"primary_keys": id}
if output_fields is not None:
params["output_fields"] = output_fields
if partition != "":
params["partition"] = partition
remaining = self.retry_option.new_remaining(retry)
res = self.viking_db_service._retry_request("FetchIndexData", {}, json.dumps(params), remaining, self.retry_option)
res = json.loads(res)
# res["data"]是一个list
# print(res["data"][0]["fields"])
fields = {}
if "fields" in res["data"][0]:
fields = res["data"][0]["fields"]
data = Data(fields, id=id, timestamp=None)
return data
elif isinstance(id, List):
datas = []
params = {"collection_name": self.collection_name, "index_name": self.index_name,
"primary_keys": id}
if output_fields is not None:
params["output_fields"] = output_fields
if partition != "":
params["partition"] = partition
remaining = self.retry_option.new_remaining(retry)
res = self.viking_db_service._retry_request("FetchIndexData", {}, json.dumps(params), remaining, self.retry_option)
res = json.loads(res)
# print(res)
for item in res["data"]:
# print(item)
fields = {}
if "fields" in item:
# print(item["fields"])
fields = item["fields"]
data = Data(fields, id=item[self.primary_key], timestamp=None)
datas.append(data)
return datas
async def async_fetch_data(self, id: Union[str, List[str], int, List[int]], output_fields=None, partition=""):
params = {}
if isinstance(id, str) or isinstance(id, int):
params = {"collection_name": self.collection_name, "index_name": self.index_name,
"primary_keys": id}
if output_fields is not None:
params["output_fields"] = output_fields
if partition != "":
params["partition"] = partition
res = await self.viking_db_service.async_get_body_exception("FetchIndexData", {}, json.dumps(params))
res = json.loads(res)
# res["data"]是一个list
# print(res["data"][0]["fields"])
fields = {}
if "fields" in res["data"][0]:
fields = res["data"][0]["fields"]
data = Data(fields, id=id, timestamp=None)
return data
elif isinstance(id, List):
datas = []
params = {"collection_name": self.collection_name, "index_name": self.index_name,
"primary_keys": id}
if output_fields is not None:
params["output_fields"] = output_fields
if partition != "":
params["partition"] = partition
res = await self.viking_db_service.async_get_body_exception("FetchIndexData", {}, json.dumps(params))
res = json.loads(res)
# print(res)
for item in res["data"]:
# print(item)
fields = {}
if "fields" in item:
# print(item["fields"])
fields = item["fields"]
data = Data(fields, id=item[self.primary_key], timestamp=None)
datas.append(data)
return datas