Feast 向量数据库(Alpha):从 Embedding 存储到 OpenAI 兼容检索的完整实践
【免费下载链接】feastThe Open Source Feature Store for AI/ML项目地址: https://gitcode.com/GitHub_Trending/fe/feast
功能状态说明:向量数据库是 Feast 的一项实验性(Alpha)功能。官方文档声明该功能目前总体稳定,但在使用体验上仍存在一些粗糙之处,欢迎社区贡献改进。本文基于当前仓库的 alpha-vector-database.md 文档及 sdk/python/feast 源码编写,帮助读者掌握如何用 Feast 存储与检索 Embedding,并接入 RAG 与 LLM 应用。
1. 概览:Feast 如何抽象向量检索
向量数据库允许用户存储并检索 Embedding 向量。Feast 提供了通用 API来完成 Embedding 的写入与相似度检索,其核心思路是:把"带向量的 Feature View"物化到支持向量检索的在线存储中,再通过统一的 SDK / HTTP 接口完成 top-k 相似度搜索。
从源码结构看,这一能力由 feature_store.py 中的retrieve_online_documents/retrieve_online_documents_v2两个方法,以及 vector_store.py 中的FeastVectorStore封装类共同承担。其中FeastVectorStore是一个便捷门面,同时支持文本、图像与多模态检索,它会在初始化时自动扫描 Feature View 中带vector_index的字段并读取其vector_search_metric作为默认距离度量(参见 vector_store.py)。
2. 支持的向量数据库与集成矩阵
官方文档给出了当前仓库中已集成的向量数据库及其能力矩阵:
| 向量数据库 | 检索 (Retrieval) | 索引 (Indexing) | V2 支持* | 在线读取 (Online Read) |
|---|---|---|---|---|
| Pgvector | ✔️ | — | — | — |
| Elasticsearch | ✔️ | ✔️ | — | — |
| Milvus | ✔️ | ✔️ | ✔️ | ✔️ |
| Faiss | — | — | — | — |
| SQLite | ✔️ | — | ✔️ | ✔️ |
| Qdrant | ✔️ | ✔️ | — | — |
| ScyllaDB | ✔️ | ✔️ | ✔️ | ✔️ |
*V2 支持:指 SDK 支持在向量相似度检索的同时,一并取回标准特征(Feature)与向量 Embedding。
两点补充说明:
- SQLite 处于受限访问状态,目前仅在 Python 3.10 上可用,其能力会随 sqlite-vec 项目进展而更新;
- 从仓库测试与示例看,Milvus、SQLite、ScyllaDB已在 SDK 层实现了 v2 方法
retrieve_online_documents_v2,相关实现在 milvus.py、sqlite.py、scylladb.py 中。
3. API 演进:retrieve_online_documents与retrieve_online_documents_v2
官方文档对两代检索方法给出了明确的演进说明(摘自文档中的 danger 提示):
未来 SDK 将弃用
retrieve_online_documents,官方推荐改用retrieve_online_documents_v2。v2 的优势在于:可以直接在 Feature View 上配置向量索引,并且能够在返回向量 Embedding 的同时取回标准特征,便于做更丰富的上下文注入。长远来看,两个方法最终会合并为一个;届时retrieve_online_documents与retrieve_online_documents_v2都会指向get_online_features,以兼容业界标准命名约定。
两代方法在源码中的差异也印证了这一演进方向:
retrieve_online_documents(feature_store.py)只接受Embedding 向量作为查询(query: Union[str, List[float]]),若直接传字符串会抛出ValueError,提示需先完成文本 Embedding;且一次只支持单个 Feature View(feature_view_set长度大于 1 即报错)。retrieve_online_documents_v2(feature_store.py)支持query(文本 Embedding)、query_string(混合检索文本)、query_image_bytes(图像相似度检索)三种查询形态,并支持combine_with_text多模态融合(text_weight + image_weight必须等于 1.0,否则报错)、filters元数据过滤,以及可选的include_feature_view_version_metadata版本元数据。
此外,v2 还内置了对 On-Demand Feature View 的支持(会从available_odfv_views中解析),使检索链路可以衔接在线变换。
4. Feature Server 检索端点总览
官方文档给出了 Feature Server 对外暴露的检索端点速查表:
| 端点 | 适用场景 |
|---|---|
POST /search | 你已持有 Embedding 向量(或使用api_version: 2传query_string),希望获得 Feast 原生的在线特征响应格式 |
GET /v1/vector_stores | 发现可用的向量存储及其vs_{hash}ID(OpenAI 兼容) |
GET /v1/vector_stores/{id} | 获取某个具体向量存储的元数据(OpenAI 兼容) |
POST /v1/vector_stores/{id}/search | 使用纯文本查询 + 服务端 Embedding,返回 OpenAI 兼容响应 |
POST /retrieve-online-documents已弃用,请改用POST /search。这一点在 feature_server.py 中也有体现:/retrieve-online-documents路由会先返回弃用提示,再转发到与/search相同的处理逻辑。
5. [Alpha] OpenAI 兼容向量存储 API
Alpha 特性警告:该 API 面已可运行且经过测试,但可能在后续版本中调整,欢迎反馈与贡献。
Feast 暴露了一组OpenAI 兼容的向量存储端点,让客户端可以用纯文本查询 + 服务端 Embedding来发现、查看与检索向量存储。这意味着AI Agent、LLM 工具调用框架以及任何 OpenAI 兼容客户端都可以直接对接,调用方无需自行产出原始 Embedding 向量。
5.1 向量存储 ID 的生成规则
每个包含至少一个vector_index=True字段的 Feature View,都会被自动分配一个确定性标识符,形如vs_{hash},其中{hash}是SHA-256(project + ":" + feature_view_name)的前 24 个字符。该 ID 在服务重启与 Registry 刷新后保持稳定。
例如,项目my_project下的 Feature Viewproduct_catalog总是映射到同一个vs_...标识。列出端点会返回这些 ID,客户端可以在运行时发现存储。
这一规则在 vector_store_utils.py 中有精确实现:
def feature_view_to_vs_id(project: str, feature_view_name: str) -> str: digest = hashlib.sha256(f"{project}:{feature_view_name}".encode()).hexdigest()[:24] return f"vs_{digest}"5.2 端点与权限
| 方法 | 路径 | 所需权限 | 说明 |
|---|---|---|---|
GET | /v1/vector_stores | DESCRIBE | 列出调用方有权限访问的全部向量存储 |
GET | /v1/vector_stores/{vector_store_id} | DESCRIBE | 获取单个向量存储的元数据 |
POST | /v1/vector_stores/{vector_store_id}/search | READ_ONLINE | 用纯文本查询检索向量存储 |
配置了认证后,以上所有端点都会强制RBAC校验;列出端点会自动过滤掉调用方无权DESCRIBE的存储。这些路由定义于 feature_server.py,服务端通过store.openai_search(...)处理POST .../search请求。
5.3 启用前置条件
要使用该 API,需要满足以下三点:
① 配置 Embedding 模型—— 在feature_store.yaml中增加embedding_model段。默认使用Sentence Transformers做本地 Embedding,无需外部 API Key(安装pip install sentence-transformers即可):
embedding_model: provider: sentence_transformers # 默认值,可省略 model: all-MiniLM-L6-v2② 定义带向量索引的 Feature View—— 至少有一个 Feature View 在向量字段上设置了vector_index=True,并且物化到支持向量检索的在线存储中。
③ (可选)数值过滤支持—— 若元数据过滤涉及数值或布尔比较,需要在在线存储配置中开启enable_openai_compatible_store: true,然后执行feast apply以在数据库中新增value_num列。
5.4 自定义 Embedding Provider
内置的 Sentence Transformers Provider 适用于大多数场景。若想接入其他 Embedding 后端(OpenAI、Cohere 或自研模型),可以实现EmbeddingProvider协议并将实例传给FeatureStore:
from feast.embedder import EmbeddingProvider class MyEmbeddingProvider: def embed(self, texts: list[str]) -> list[list[float]]: # 在此调用你的 Embedding API return my_model.encode(texts) async def aembed(self, texts: list[str]) -> list[list[float]]: return await my_model.aencode(texts) store = FeatureStore( repo_path=".", embedding_provider=MyEmbeddingProvider(), )从源码看,该协议定义于 embedder.py,内置的SentenceTransformersEmbeddingProvider(embedder.py)通过from_config工厂构造,模型加载采用惰性初始化——只有真正调用embed/aembed时才导入 sentence-transformers,避免启动开销。get_embedding_provider工厂会按config.provider分发(默认sentence_transformers),未知 Provider 名称会抛出ValueError。
5.5 数值存储:enable_openai_compatible_store
默认情况下,特征值在在线存储中以文本形式保存,因此字符串排序规则生效(例如'9' > '100'为true)。当在线存储配置中开启enable_openai_compatible_store: true后,Feast 会新增一个value_num列,用原生类型存储int、float、double和bool值,从而保证数值过滤结果正确:
online_store: type: postgres # 或 sqlite # ... 连接配置 ... enable_openai_compatible_store: true修改该配置后,需运行feast apply更新数据库表结构。
5.6 端点使用示例
以下请求默认 Feature Server 运行在localhost:6566(feast serve启动)。
列出向量存储:
curl http://localhost:6566/v1/vector_stores{ "object": "list", "data": [ { "id": "vs_a1b2c3d4e5f6a1b2c3d4e5f6", "object": "vector_store", "name": "product_catalog", "status": "completed", "created_at": 1717200000 } ] }获取单个向量存储:
curl http://localhost:6566/v1/vector_stores/vs_a1b2c3d4e5f6a1b2c3d4e5f6返回对象形状与列表响应中的单个条目一致;若 ID 不匹配任何带向量索引的 Feature View,返回404。
执行检索:
curl -X POST http://localhost:6566/v1/vector_stores/vs_a1b2c3d4e5f6a1b2c3d4e5f6/search \ -H "Content-Type: application/json" \ -d '{ "query": "wireless noise-cancelling headphones", "max_num_results": 5 }'5.7 请求字段说明
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
query | string或list[string] | 必填 | 纯文本检索查询。列表会在 Embedding 前用空格拼接 |
max_num_results | int | 10 | 返回结果的最大条数 |
filters | object | null | OpenAI 风格过滤器(见下文) |
ranking_options | object | null | 为向前兼容而接受,但当前被忽略;在其中设置score_threshold或ranker将返回 422 错误 |
rewrite_query | bool | null | 接受false(默认/无操作);true尚未支持,将返回 422 错误 |
metadata | object | null | 可选;metadata.features_to_retrieve用于挑选特定特征 |
5.8 元数据过滤器(OpenAI 风格)
端点支持在向量相似度之外,用 OpenAI 风格过滤器进一步收窄结果。
比较运算符:eq、ne、gt、gte、lt、lte、in、nin
{"type": "eq", "key": "category", "value": "Electronics"}复合运算符:and、or(可任意深度嵌套)
{ "type": "and", "filters": [ {"type": "eq", "key": "category", "value": "Electronics"}, {"type": "gte", "key": "rating", "value": 4.5} ] }使用限制:
- 对于Postgres 与 SQLite后端,所有过滤(包括字符串相等比较)都要求在线存储开启
enable_openai_compatible_store: true;开启后执行feast apply更新表结构; - ScyllaDB支持通过
retrieve_online_documents_v2进行向量检索,但OpenAI 风格元数据过滤尚未实现——传入filters会抛出NotImplementedError。
5.9 响应格式与分数转换
响应遵循 OpenAIvector_store.search_results.page模式:
{ "object": "vector_store.search_results.page", "search_query": ["wireless noise-cancelling headphones"], "data": [ { "file_id": "vs_a1b2c3d4e5f6a1b2c3d4e5f6_42", "filename": "vs_a1b2c3d4e5f6a1b2c3d4e5f6", "score": 0.92, "attributes": {"name": "...", "category": "..."}, "content": [ {"type": "text", "text": "..."} ] } ], "has_more": false, "next_page": null }要点解读:
file_id与filename字段使用vs_{hash}标识符,而非原始 Feature View 名称;score是越高越好的相关性分数,由原始向量距离按度量类型换算而来:
| 距离度量 | 换算公式 | 取值范围 |
|---|---|---|
| L2(默认) | 1 / (1 + distance) | (0, 1] |
| Cosine | 1 - distance | [0, 1] |
| 内积 / 点积 | -distance | 不定 |
- 度量由 Feature View 向量字段上的
vector_search_metric决定,不由 API 参数指定。该换算逻辑在 utils.py 的_distance_to_score中实现; - 省略
features_to_retrieve时,默认返回全部非向量特征(向量 Embedding 列会被排除); - 分页尚未实现,
has_more恒为false。
从 feature_store.py 的openai_search实现可以看到完整的响应组装链路:先经embedding_provider.aembed对查询文本做 Embedding,再调用retrieve_online_documents_v2获取结果,最后逐行将distance换算为score、把字符串特征组装进content、并用排序后的实体键拼接file_id(无实体时回退为下标)。
5.10 通过 Python SDK 直接调用
OpenAI 兼容检索也可直接通过 Python SDK 使用(注意openai_search是异步方法):
import asyncio from feast import FeatureStore store = FeatureStore(repo_path=".") result = asyncio.run(store.openai_search( vector_store_id="product_catalog", query="wireless noise-cancelling headphones", max_num_results=5, filters={"type": "eq", "key": "category", "value": "Electronics"}, )) for item in result["data"]: print(f"{item['score']:.3f} {item['attributes']}")SDK 参数与 HTTP 端点一一对应:vector_store_id对应 Feature View 名称(内部映射为vs_{hash}),filters既支持字典也支持ComparisonFilter/CompoundFilter类型对象。
5.11 各在线存储的 OpenAI 兼容支持情况
官方文档确认已为下列在线存储实现了 OpenAI 兼容过滤:
| 在线存储 | 向量检索 | 元数据过滤 | 备注 |
|---|---|---|---|
| Milvus | ✔️ | ✔️ | 布尔表达式 |
| Elasticsearch | ✔️ | ✔️ | Query DSL 子句 |
| Postgres(pgvector) | ✔️ | ✔️ | 需enable_openai_compatible_store: true |
| SQLite(sqlite-vec) | ✔️ | ✔️ | 需enable_openai_compatible_store: true |
| MongoDB | ✔️ | ✔️ | 聚合管道 |
| ScyllaDB | ✔️ | ❌ | 仅向量检索;元数据过滤暂未支持 |
6. 端到端示例:Milvus 上的 RAG 检索
官方文档给出了一条完整的 RAG 链路(v1 路线,基于retrieve_online_documents_v2),完整可运行示例见仓库中的 examples/rag/milvus-quickstart.ipynb(配套的 Feature Repo 位于 examples/rag/feature_repo)。v0 路线的retrieve_online_documents用法见 Rag Demo 的 module_4_rag(官方已计划迁移与弃用)。
6.1 准备离线 Embedding 数据集
运行以下命令生成 Embedding 数据集:
python pull_states.py python batch_score_documents.py输出将存储在data/city_wikipedia_summaries.csv中。
6.2 初始化 Feature Store 并物化到在线存储
使用下面的feature_store.yaml初始化特征仓库:数据源使用本地文件作为离线存储,Milvus 作为在线存储:
project: local_rag provider: local registry: data/registry.db online_store: type: milvus path: data/online_store.db vector_enabled: true embedding_dim: 384 index_type: "IVF_FLAT" offline_store: type: file entity_key_serialization_version: 3 # 默认 no_auth;其他可选值:kubernetes、oidc,详见相关文档 auth: type: no_auth然后在终端执行:
feast apply执行feast apply时,会注册下面这个后续用于检索的 Feature View:
document_embeddings = FeatureView( name="embedded_documents", entities=[item, author], schema=[ Field( name="vector", dtype=Array(Float32), # 看,开启 RAG 就是这么简单! vector_index=True, vector_search_metric="COSINE", ), Field(name="item_id", dtype=Int64), Field(name="author_id", dtype=String), Field(name="created_timestamp", dtype=UnixTimestamp), Field(name="sentence_chunks", dtype=String), Field(name="event_timestamp", dtype=UnixTimestamp), ], source=rag_documents_source, ttl=timedelta(hours=24), )随后用 SDK 把包含 Embedding 的 DataFrame 写入在线存储:
store.write_to_online_store(feature_view_name='city_embeddings', df=df)6.3 生成查询 Embedding
推理阶段(例如用户提交聊天消息时)需要对输入文本做 Embedding。这可以看作对输入数据的一次特征变换。示例中使用 Hugging Face 的小型 Sentence Transformer 完成:
import torch import torch.nn.functional as F from feast import FeatureStore from pymilvus import MilvusClient, DataType, FieldSchema from transformers import AutoTokenizer, AutoModel from example_repo import city_embeddings_feature_view, item TOKENIZER = "sentence-transformers/all-MiniLM-L6-v2" MODEL = "sentence-transformers/all-MiniLM-L6-v2" def mean_pooling(model_output, attention_mask): token_embeddings = model_output[ 0 ] # model_output 的第一个元素包含所有 token 的 Embedding input_mask_expanded = ( attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float() ) return torch.sum(token_embeddings * input_mask_expanded, 1) / torch.clamp( input_mask_expanded.sum(1), min=1e-9 ) def run_model(sentences, tokenizer, model): encoded_input = tokenizer( sentences, padding=True, truncation=True, return_tensors="pt" ) # 计算 token Embedding with torch.no_grad(): model_output = model(**encoded_input) sentence_embeddings = mean_pooling(model_output, encoded_input["attention_mask"]) sentence_embeddings = F.normalize(sentence_embeddings, p=2, dim=1) return sentence_embeddings question = "Which city has the largest population in New York?" tokenizer = AutoTokenizer.from_pretrained(TOKENIZER) model = AutoModel.from_pretrained(MODEL) query_embedding = run_model(question, tokenizer, model).detach().cpu().numpy().tolist()[0]6.4 检索 top-K 相似文档
创建 Feature Store 实例,调用retrieve_online_documents_v2检索与查询最相似的 3 条文档:
context_data = store.retrieve_online_documents_v2( features=[ "city_embeddings:vector", "city_embeddings:item_id", "city_embeddings:state", "city_embeddings:sentence_chunks", "city_embeddings:wiki_summary", ], query=query_embedding, top_k=3, distance_metric='COSINE', ).to_df()注意这里features以feature_view:feature形式引用多个特征,一次调用即可同时取回向量相似度结果与文档相关的标准特征,这正是 v2 方法的核心价值。
6.5 用 LLM 生成最终回答
假设已有基础提示词BASE_PROMPT和格式化检索结果的函数format_documents,可以将检索到的上下文拼入提示词后调用 OpenAI 的 Chat Completion API:
FULL_PROMPT = format_documents(rag_context_data, BASE_PROMPT) from openai import OpenAI client = OpenAI( api_key=os.environ.get("OPENAI_API_KEY"), ) response = client.chat.completions.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": FULL_PROMPT}, {"role": "user", "content": question} ], ) # 输出回答内容。端到端示例见 examples/rag/milvus-quickstart.ipynb print('\n'.join([c.message.content for c in response.choices]))至此,"文档入库 → 向量检索 → 上下文注入 → LLM 生成"的完整 RAG 闭环就打通了。
7. 配置与安装
Feast 为向量数据库场景提供以下在线存储选项:Milvus、PGVector、SQLite(sqlite-vec)、Elasticsearch、Qdrant。其中 Milvus 提供了便捷的本地实现用于向量相似度检索。
按需选择对应的安装方式:
# Milvus pip install feast[milvus] # Elasticsearch pip install feast[elasticsearch] # Qdrant pip install feast[qdrant]SQLite 特殊说明:若使用pyenv管理 Python 版本,需要先安装带可加载扩展支持的 Python 3.10:
PYTHON_CONFIGURE_OPTS="--enable-loadable-sqlite-extensions" \ LDFLAGS="-L/opt/homebrew/opt/sqlite/lib" \ CPPFLAGS="-I/opt/homebrew/opt/sqlite/include" \ pyenv install 3.10.14然后安装 Feast 的 SQLite 向量扩展包:
pip install feast[sqlite_vec]8. 小结与建议
- 新项目直接使用 v2 路线:
retrieve_online_documents_v2支持在 Feature View 上直接声明向量索引(vector_index=True+vector_search_metric),并能在检索向量的同时取回标准特征,是官方推荐的长期方案; - AI Agent 集成首选 OpenAI 兼容 API:
/v1/vector_stores/*系列端点支持纯文本查询与服务端 Embedding,配合 embedder.py 的EmbeddingProvider协议可以无缝替换任意 Embedding 后端,无需调用方关心向量细节; - 数值过滤需显式开启:Postgres / SQLite 上若元数据过滤涉及数值或布尔比较,务必开启
enable_openai_compatible_store: true并重新执行feast apply; - 关注功能演进:该 API 处于 Alpha 阶段,
retrieve_online_documents已进入弃用计划,ranking_options、rewrite_query等参数尚未实现,使用时建议以当前仓库文档与源码为准。
【免费下载链接】feastThe Open Source Feature Store for AI/ML项目地址: https://gitcode.com/GitHub_Trending/fe/feast
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考