data_router.py
5.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
from typing import List, Optional, Literal, Union, Dict, Any
from fastapi import APIRouter, Depends
import numpy
from pydantic import BaseModel, Field, TypeAdapter
from embedding_sever.api.resp_bean import RespBean, success
router = APIRouter()
# ============== 请求/响应模型定义 ==============
class FaceKwargs(BaseModel):
"""人脸类型扩展字段"""
person_id: Optional[str] = Field(None, description="人物id")
person_name: Optional[str] = Field(None, description="人物姓名")
class SportShotKwargs(BaseModel):
"""体育镜头类型扩展字段"""
match_name: Optional[str] = Field(None, description="比赛名称")
person_name: Optional[str] = Field(None, description="人名")
video_desc: Optional[str] = Field(None, description="视频描述")
shot_cls: Optional[str] = Field(None, description="镜头分类")
match_id: Optional[str] = Field(None, description="比赛id")
program_id: Optional[str] = Field(None, description="节目id")
# 定义类型映射,用于根据type自动转换kwargs
KwargsType = Union[FaceKwargs, SportShotKwargs]
class PutDataRequest(BaseModel):
"""写入数据请求体"""
id: str = Field(..., description="唯一键id")
embedding: List[float] = Field(..., description="向量")
embedding_version: str = Field(..., description="向量版本 小写数字下划线组成")
kwargs: Optional[Dict[str, Any]] = Field(None, description="扩展字段,根据type不同字段不同")
def get_typed_kwargs(self, type: str) -> Optional[KwargsType]:
"""根据type自动转换kwargs为具体类型"""
if self.kwargs is None:
return None
type_map = {
'face': FaceKwargs,
'sport_shot': SportShotKwargs
}
kwargs_class = type_map.get(type)
if kwargs_class:
return kwargs_class(**self.kwargs)
return None
class DelByIdRequest(BaseModel):
"""删除数据请求体"""
ids: List[str] = Field(..., description="id 集合")
embedding_version: str = Field(..., description="向量版本 小写数字下划线组成")
class FilterItem(BaseModel):
"""过滤条件项"""
name: str = Field(..., description="字段名")
value: List = Field(..., description="字段值")
opt: Optional[Literal["eq", "neq", "lt", "gt", "lte", "gte", "like", "in"]] = Field(
None, description="操作类型: eq相等, neq不等, lt小于, gt大于, lte小于等于, gte大于等于, like模糊匹配, in内"
)
class SearchRequest(BaseModel):
"""检索数据请求体"""
embedding: List[float] = Field(..., description="向量")
embedding_version: str = Field(..., description="向量版本 小写数字下划线组成")
topk: int = Field(..., description="top k")
filters: Optional[List[FilterItem]] = Field(None, description="过滤字段")
class SearchResultItem(BaseModel):
"""检索结果单项"""
id: str = Field(..., description="id")
kwargs: dict = Field(..., description="扩展字段")
score: float = Field(..., description="分值")
class SearchResponseData(BaseModel):
"""检索响应数据"""
type: str = Field(..., description="type")
data_list: List[SearchResultItem] = Field(..., description="topk结果列表")
# ============== 接口定义 ==============
from embedding_sever.service.data_service import DataService
from embedding_sever.service import get_data_service
@router.put("/{type}/put", response_model=RespBean, tags=["数据服务"], summary="写入数据")
@router.post("/{type}/put", response_model=RespBean, tags=["数据服务"], summary="写入数据")
async def put_data(
type: Literal["face", "sport_shot"],
request: PutDataRequest,
data_service: DataService = Depends(get_data_service)
):
"""
写入数据
"""
request.embedding = numpy.array(request.embedding)
embedding_version = request.embedding_version
tb_name = f'{type}_{embedding_version}'
upserted_id = await data_service.upsert(tb_name, request.id, request.embedding,
request.get_typed_kwargs(type).model_dump())
return success(data={'id': upserted_id})
@router.delete("/{type}/del_by_id", response_model=RespBean, tags=["数据服务"], summary="删除数据")
@router.post("/{type}/del_by_id", response_model=RespBean, tags=["数据服务"], summary="删除数据")
async def del_by_id(
type: Literal["face", "sport_shot"],
request: DelByIdRequest,
data_service: DataService = Depends(get_data_service)
):
"""
删除数据
- **type**: 数据类型 (face-人脸, sport_shot-体育镜头)
- **ids**: id集合
"""
embedding_version = request.embedding_version
tb_name = f'{type}_{embedding_version}'
await data_service.delete_by_pks(tb_name, request.ids)
return success()
@router.post("/{type}/search", response_model=RespBean[SearchResponseData], tags=["数据服务"], summary="检索数据")
async def search_data(
type: Literal["face", "sport_shot"],
request: SearchRequest,
data_service: DataService = Depends(get_data_service),
adapter=TypeAdapter(List[SearchResultItem])
):
"""
检索数据
- **type**: 数据类型 (face-人脸, sport_shot-体育镜头)
- **embedding**: 查询向量
- **embedding_version**: 向量版本
- **topk**: 返回top k条结果
- **filters**: 过滤条件列表
"""
#
embedding_version = request.embedding_version
tb_name = f'{type}_{embedding_version}'
embedding = request.embedding
filters = request.filters
from_, size = 0, request.topk
result_data = await data_service.search(tb_name, embedding, filters, from_, size)
search_results: List[SearchResultItem] = adapter.validate_python(result_data)
return success(data=SearchResponseData(type=type, data_list=search_results))