- 人工智能
- 大模型
- AI Agent
- 工作流自动化
- RAG
【免费下载链接】PocketFlow
Pocket Flow: 100-line LLM framework. Let Agents build Agents!
导读
在 LLM 应用中,向量检索是 RAG(检索增强生成)、记忆召回、语义搜索等能力的核心底座:文本先被 embedding 模型映射为稠密向量,再由向量数据库完成最近邻检索。本文以 PocketFlow 官方文档《Vector Databases》为主体,完整梳理 FAISS、Pinecone、Qdrant、Weaviate、Milvus、Chroma、Redis 七种主流向量检索方案的免费额度、定价模型与 Python 接入代码,并结合仓库内的 RAG 设计模式 与 Chat with Memory 示例 的源码实现,说明如何把向量检索真正接入 PocketFlow 的 Node/Flow 流程。读完本文,你将掌握各方案的选型要点、可复制的接入代码,以及一套与 PocketFlow Agent 工作流结合的落地范式。
一、向量检索在 LLM 应用中的位置:为什么要选型
向量数据库解决的是"语义相似度检索"问题。在 PocketFlow 的 RAG 设计模式 中,完整的 RAG 管线被拆成两个阶段:
- 离线阶段(构建索引):文档分块(Chunk)→ 每个块生成 Embedding → 把向量存入向量数据库;
- 在线阶段(检索问答):用户问题生成 Embedding → 在索引中检索最相关的块 → 把问题与上下文一起交给 LLM 生成答案。
其中"生成 Embedding"对应文档 Embedding,"存入向量数据库/执行检索"正是本文要展开的环节。二者共同构成 RAG 的语义检索链路:Embedding 负责把文本变成向量,向量数据库负责在向量空间里快速找"最近邻"。因此,选型向量数据库的实质,是在部署成本、检索性能、功能丰富度、与现有技术栈的契合度之间做权衡。
二、主流向量检索方案横向对比
文档 Vector Databases 给出了一份主流向量检索方案对比表,覆盖免费额度与定价模型两个关键选型维度:
| 工具 | 免费额度(Free Tier) | 定价模型(Pricing Model) | 说明 |
|---|---|---|---|
| FAISS | 无,自托管(self-host) | 开源 | Meta 开源的向量索引库,非独立数据库服务,内嵌在应用进程中 |
| Pinecone | 2GB 免费 | 每月 25 美元起 | 全托管云服务,无需运维索引 |
| Qdrant | 1GB 免费云额度 | 按量付费(pay-as-you-go) | Rust 实现,支持过滤与负载均衡 |
| Weaviate | 14 天沙箱试用 | 每月 25 美元起 | 自带 GraphQL 查询层与多种模块 |
| Milvus | 5GB 免费云额度 | 按量付费或每月 99 美元专属实例 | 面向大规模向量的分布式系统,支持 GPU 加速 |
| Chroma | 无,自托管 | 免费(Apache 2.0 协议) | 轻量级嵌入式向量库,适合原型与本地开发 |
| Redis | 30MB 免费 | 每月 5 美元起 | 在既有 Redis 上叠加向量检索能力(RediSearch 模块) |
2.1 按部署形态理解这张表
- 进程内库(In-process):FAISS 和 Chroma 不需要独立服务,直接作为 Python 库/嵌入式数据库随应用运行,零运维成本,最契合原型验证与单机场景。仓库中的 Chat with Memory 示例 正是采用 FAISS 以进程内方式完成向量索引。
- 全托管云服务(Managed Cloud):Pinecone、Qdrant Cloud、Milvus Cloud、Weaviate Cloud 负责索引构建、分片与扩容,代价是网络往返延迟与按用量计费。
- 通用存储的向量扩展:Redis 借助 RediSearch 模块在 KV 存储之上提供 KNN 检索,适合已在用 Redis 的团队复用基础设施。
2.2 从文档 Embedding 得到的选型提示
文档在 Embedding 章节给出了一条重要实践建议:"Embedding 相对于 Flow 设计而言只是微优化,建议先选最顺手的方案,之后再优化。"这条原则同样适用于向量数据库选型——先用最容易跑通、免费额度最充裕的方案把流程串起来,等检索规模与性能瓶颈真正出现时再迁移,而不是在项目初期就陷入分布式调优。
三、七种方案的 Python 接入实战
以下是文档提供的各工具基础用法代码。所有示例均遵循同一逻辑:创建索引/集合 → 写入向量 → 用查询向量做最近邻检索。示例统一使用 128 维向量(d = 128)以保持一致性,实际接入时请替换为你所用 embedding 模型的真实维度。
3.1 FAISS(开源自托管)
import faiss import numpy as np # Dimensionality of embeddings d = 128 # Create a flat L2 index index = faiss.IndexFlatL2(d) # Random vectors data = np.random.random((1000, d)).astype('float32') index.add(data) # Query query = np.random.random((1, d)).astype('float32') D, I = index.search(query, k=5) print("Distances:", D) print("Neighbors:", I)要点说明:
IndexFlatL2是暴力精确检索(exhaustive search),适合中小规模索引;数据量增大后可替换为IndexIVFFlat(倒排+量化)等近似最近邻索引。- 输入向量必须是
float32的 NumPy 数组,这是 FAISS 的性能前提。 - 返回值
D是距离矩阵,I是命中向量的下标矩阵(shape 均为(查询数, k))。
3.2 Pinecone(全托管云服务)
import pinecone pinecone.init(api_key="YOUR_API_KEY", environment="YOUR_ENV") index_name = "my-index" # Create the index if it doesn't exist if index_name not in pinecone.list_indexes(): pinecone.create_index(name=index_name, dimension=128) # Connect index = pinecone.Index(index_name) # Upsert vectors = [ ("id1", [0.1]*128), ("id2", [0.2]*128) ] index.upsert(vectors) # Query response = index.query([[0.15]*128], top_k=3) print(response)要点说明:
upsert的每个元素是(id, vector)二元组,id用于后续删除与更新。- 查询时只需传入查询向量与
top_k,服务端自动完成最近邻计算并返回相似度得分。 - 代码中的
environment参数与新版 Pinecone 客户端 API 存在差异,实际使用时以你所安装客户端版本的初始化方式为准。
3.3 Qdrant(Rust 高性能方案)
import qdrant_client from qdrant_client.models import Distance, VectorParams, PointStruct client = qdrant_client.QdrantClient( url="https://YOUR-QDRANT-CLOUD-ENDPOINT", api_key="YOUR_API_KEY" ) collection = "my_collection" client.recreate_collection( collection_name=collection, vectors_config=VectorParams(size=128, distance=Distance.COSINE) ) points = [ PointStruct(id=1, vector=[0.1]*128, payload={"type": "doc1"}), PointStruct(id=2, vector=[0.2]*128, payload={"type": "doc2"}), ] client.upsert(collection_name=collection, points=points) results = client.search( collection_name=collection, query_vector=[0.15]*128, limit=2 ) print(results)要点说明:
- Qdrant 的亮点是payload 附带与过滤:每个
PointStruct可携带任意元数据(如示例中的type字段),检索时可按 payload 条件先过滤再搜索。 - 距离度量通过
Distance.COSINE显式声明,可选COSINE、EUCLID(L2)、DOT(点积)。 recreate_collection会重建集合,生产环境应改用create_collection或先判断集合是否存在。
3.4 Weaviate(GraphQL 语义层)
import weaviate client = weaviate.Client("https://YOUR-WEAVIATE-CLOUD-ENDPOINT") schema = { "classes": [ { "class": "Article", "vectorizer": "none" } ] } client.schema.create(schema) obj = { "title": "Hello World", "content": "Weaviate vector search" } client.data_object.create(obj, "Article", vector=[0.1]*128) resp = ( client.query .get("Article", ["title", "content"]) .with_near_vector({"vector": [0.15]*128}) .with_limit(3) .do() ) print(resp)要点说明:
- 通过 Schema 先定义类(class),
vectorizer: "none"表示向量由外部提供(即使用你自己的 embedding 模型)。 - 查询走 GraphQL 语法:
get指定类与返回字段,with_near_vector传入查询向量,with_limit控制返回条数。 - Weaviate 同样支持把向量化模型作为模块内置,实现"文本进、结果出"的端到端能力。
3.5 Milvus(大规模分布式检索)
from pymilvus import connections, FieldSchema, CollectionSchema, DataType, Collection import numpy as np connections.connect(alias="default", host="localhost", port="19530") fields = [ FieldSchema(name="id", dtype=DataType.INT64, is_primary=True), FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=128) ] schema = CollectionSchema(fields) collection = Collection("MyCollection", schema) emb = np.random.rand(10, 128).astype('float32') ids = list(range(10)) collection.insert([ids, emb]) index_params = { "index_type": "IVF_FLAT", "params": {"nlist": 128}, "metric_type": "L2" } collection.create_index("embedding", index_params) collection.load() query_emb = np.random.rand(1, 128).astype('float32') results = collection.search(query_emb, "embedding", param={"nprobe": 10}, limit=3) print(results)要点说明:
- Milvus 的流程更偏"数据库工程":先
connect到服务,再定义字段 Schema 创建集合。 - 插入后需要显式建索引(
IVF_FLAT、nlist控制聚类数量)并load()到内存,才能执行search。 search中的nprobe是 IVF 检索时探测的聚类数,越大召回越准、耗时越长;metric_type支持L2、IP(内积)、COSINE。
3.6 Chroma(轻量嵌入式,Apache 2.0)
import chromadb from chromadb.config import Settings client = chromadb.Client(Settings( chroma_db_impl="duckdb+parquet", persist_directory="./chroma_data" )) coll = client.create_collection("my_collection") vectors = [[0.1, 0.2, 0.3], [0.2, 0.2, 0.2]] metas = [{"doc": "text1"}, {"doc": "text2"}] ids = ["id1", "id2"] coll.add(embeddings=vectors, metadatas=metas, ids=ids) res = coll.query(query_embeddings=[[0.15, 0.25, 0.3]], n_results=2) print(res)要点说明:
Settings中的persist_directory指定持久化目录,duckdb+parquet为旧版默认存储引擎;新版 Chroma 已调整了持久化配置方式,使用时请对照所装版本的 API。add同时接收embeddings、metadatas、ids三组数据,元数据随向量一并存储。- 不传
embeddings时 Chroma 也可调用内置 embedding 函数自动向量化,适合快速原型。
3.7 Redis(在 KV 存储上做 KNN)
import redis import struct r = redis.Redis(host="localhost", port=6379) # Create index r.execute_command( "FT.CREATE", "my_idx", "ON", "HASH", "SCHEMA", "embedding", "VECTOR", "FLAT", "6", "TYPE", "FLOAT32", "DIM", "128", "DISTANCE_METRIC", "L2" ) # Insert vec = struct.pack('128f', *[0.1]*128) r.hset("doc1", mapping={"embedding": vec}) # Search qvec = struct.pack('128f', *[0.15]*128) q = "*=>[KNN 3 @embedding $BLOB AS dist]" res = r.ft("my_idx").search(q, query_params={"BLOB": qvec}) print(res.docs)要点说明:
- 依赖 Redis 的 RediSearch 模块(
FT.CREATE命令),需确保 Redis 服务端已加载该模块。 - 向量先通过
struct.pack('128f', ...)打包为二进制BLOB存入 Hash 字段。 - 检索语法
*=>[KNN 3 @embedding $BLOB AS dist]表示对embedding字段执行 KNN 检索、返回前 3 个结果并把距离别名设为dist;$BLOB由query_params注入查询向量。
四、把向量检索接入 PocketFlow:仓库源码级实现
理解了各方案的接入代码之后,关键问题是如何把它变成 PocketFlow 流程中的一个环节。仓库中的 Chat with Memory 示例 提供了完整的参考实现,其结构是"向量索引工具层 + Node 流程层"两层设计。
4.1 工具层:对 FAISS 的统一封装
utils/vector_index.py 把 FAISS 的琐碎细节封装为三个函数,屏蔽了向量形状转换、ntotal计数等易错点:
import numpy as np import faiss def create_index(dimension=1536): return faiss.IndexFlatL2(dimension) def add_vector(index, vector): # Make sure the vector is a numpy array with the right shape for FAISS vector = np.array(vector).reshape(1, -1).astype(np.float32) # Add the vector to the index index.add(vector) # Return the position (index.ntotal is the total number of vectors in the index) return index.ntotal - 1 def search_vectors(index, query_vector, k=1): """Search for the k most similar vectors to the query vector""" k = min(k, index.ntotal) if k == 0: return [], [] query_vector = np.array(query_vector).reshape(1, -1).astype(np.float32) distances, indices = index.search(query_vector, k) return indices[0].tolist(), distances[0].tolist()这段封装有两点值得借鉴:
add_vector返回index.ntotal - 1,即新向量在索引中的位置。由于 FAISS 索引本身不保存原始数据,这个位置号需要与业务数据(如对话记录)一一对应,由调用方维护一个平行的列表。search_vectors对k做了min(k, index.ntotal)的边界保护,避免检索数量超过索引规模时报错;k = 0时直接返回空结果,保证空索引下流程仍能正常运行。
从源码结构看,这种"业务数据列表 + FAISS 位置号"的配对方式,正是进程内向量库的标准用法:向量库负责相似度计算,业务数据留在应用侧,二者通过下标关联。
4.2 流程层:EmbedNode 与 RetrieveNode
在 nodes.py 中,向量检索被拆成两个职责单一的 Node:
写入侧EmbedNode(归档旧对话并写入索引):
def exec(self, conversation): # Combine user and assistant messages into a single text for embedding user_msg = next((msg for msg in conversation if msg["role"] == "user"), {"content": ""}) assistant_msg = next((msg for msg in conversation if msg["role"] == "assistant"), {"content": ""}) combined = f"User: {user_msg['content']} Assistant: {assistant_msg['content']}" # Generate embedding embedding = get_embedding(combined) return {"conversation": conversation, "embedding": embedding} def post(self, shared, prep_res, exec_res): # Initialize vector index if not exist if "vector_index" not in shared: shared["vector_index"] = create_index() shared["vector_items"] = [] # Track items separately # Add the embedding to the index and store the conversation position = add_vector(shared["vector_index"], exec_res["embedding"]) shared["vector_items"].append(exec_res["conversation"]) return "question"读取侧RetrieveNode(按当前问题召回最相关的历史对话):
def exec(self, inputs): query = inputs["query"] vector_index = inputs["vector_index"] vector_items = inputs["vector_items"] # Create embedding for the query query_embedding = get_embedding(query) # Search for the most similar conversation indices, distances = search_vectors(vector_index, query_embedding, k=1) if not indices: return None conversation = vector_items[indices[0]] return {"conversation": conversation, "distance": distances[0]} def post(self, shared, prep_res, exec_res): if exec_res is not None: shared["retrieved_conversation"] = exec_res["conversation"] print(f"📄 Retrieved conversation (distance: {exec_res['distance']:.4f})") else: shared["retrieved_conversation"] = None return "answer"值得注意的工程细节:
- 向量索引与业务数据都挂在
shared字典上(shared["vector_index"]与shared["vector_items"]),这符合 PocketFlow 以shared为流程共享存储的设计约定; - 检索失败(索引为空或没有命中)时返回
None,由post兜底写入shared["retrieved_conversation"] = None,下游AnswerNode据此跳过上下文注入——检索环节的失败不会中断整个对话流程; - 距离(distance)作为可解释性指标打印出来,方便调试召回质量。
4.3 流程编排
flow.py 用 PocketFlow 的动作(action)语法把写入与召回串进对话循环:
question_node - "retrieve" >> retrieve_node retrieve_node - "answer" >> answer_node answer_node - "embed" >> embed_node answer_node - "question" >> question_node embed_node - "question" >> question_node return Flow(start=question_node)这里展示了一种典型的记忆增强循环:对话进行时,最旧的对话对触发embed分支被向量化归档;下一轮提问时,retrieve分支在向量索引中找回语义最相关的历史对话注入上下文。整个循环不需要额外引入后台任务,完全由 PocketFlow 的条件跳转(Node 的post返回动作字符串,如"question"、"retrieve"、"embed"、"answer")驱动。
4.4 RAG 场景中的向量数据库接入
若目标是文档问答而非对话记忆,可参考 RAG 设计模式 与 pocketflow-rag 示例 的两阶段编排:
- 离线索引 Flow:
ChunkDocumentsNode >> EmbedDocumentsNode >> CreateIndexNode,把分块后的文档依次向量化并写入索引(flow.py); - 在线问答 Flow:
EmbedQueryNode >> RetrieveDocumentNode >> GenerateAnswerNode,对问题向量化后从索引中检索最相关块,拼接进 prompt 交给 LLM(flow.py)。
其中向量写入与检索即可替换为本文第三部分任意一种方案的接入代码——这就是"向量数据库选型"与"Agent 流程设计"的解耦点:PocketFlow 负责编排,向量库只负责被调用。
五、落地建议:在 PocketFlow 中如何选与如何接
综合文档对比表与仓库实践,给出如下落地建议:
- 原型阶段优先 FAISS 或 Chroma:进程内运行、零服务依赖,与 vector_index.py 的封装模式一致,几分钟即可跑通 RAG/记忆流程。
- 需要过滤与元数据检索时选 Qdrant 或 Weaviate:payload/GraphQL 过滤能力让"按条件缩小检索范围"变得自然,适合文档带标签、权限等业务属性的场景。
- 超大向量规模(百万级以上)考虑 Milvus:其分布式架构与显式索引管理面向生产级规模,代价是需要维护独立集群。
- 已在用 Redis 的团队可复用基础设施:通过 RediSearch 模块在既有 Redis 上叠加向量能力,但免费额度(30MB)较小,适合轻量场景。
- 全托管(Pinecone/Milvus Cloud 等)适合不想运维索引的团队:把构建索引、扩容的负担交给服务商,按量付费。
无论选择哪一种,建议保持"工具层封装 + Node 流程层调用"的结构:把向量库 API 收敛到独立的 utils 模块(如vector_index.py),Node 只依赖封装后的create_index/add_vector/search_vectors三个语义稳定的函数,这样未来切换向量数据库时只需替换工具层实现,PocketFlow 的 Node 与 Flow 编排完全不受影响。
六、小结
本文从 PocketFlow 官方文档《Vector Databases》出发,完成了三件事:
- 完整对比了七种主流向量检索方案的免费额度、定价模型与适用场景,并给出了全部 Python 接入代码,可直接复制运行;
- 结合仓库源码,剖析了 vector_index.py 的 FAISS 封装模式、nodes.py 中写入/召回 Node 的实现细节,以及 flow.py 中由动作驱动的检索循环;
- 给出了选型与落地的具体建议,明确了"向量库负责检索、PocketFlow 负责编排"的解耦原则。
向量数据库只是 RAG 链条中的一环,与之配套的文本分块与 Embedding 接入可继续阅读 Chunking 指南 和 Embedding 指南,完整的两阶段 RAG 编排见 RAG 设计模式。将本文的向量接入代码与这些模式组合,即可在 PocketFlow 中搭建出可运行的语义检索 Agent。
- 人工智能
- 大模型
- AI Agent
- 工作流自动化
- RAG
【免费下载链接】PocketFlow
Pocket Flow: 100-line LLM framework. Let Agents build Agents!
相关推荐
Quivr数据库选型:关系型vs向量数据库对比
Quivr数据库选型:关系型vs向量数据库对比 引言:AI时代的数据存储挑战 在构建智能助手应用时,传统的关系型数据库已无法满足现代AI应用对语义搜索和向量相似
人工智能AI 应用大模型RAG后端前端QuickRecorder:免费 macOS 录屏工具,5 分钟录出干净屏幕视频
QuickRecorder:免费 macOS 录屏工具,5 分钟录出干净屏幕视频 今天要录一段 30 分钟的线上会议,又不想把重型软件装进电脑?试试 Quick
桌面应用音视频屏幕录制all-in-rag 实战:向量数据库选型与 FAISS 本地向量存储实现指南
all in rag 实战:向量数据库选型与 FAISS 本地向量存储实现指南 导读 本文是 Datawhale《大模型应用开发实战一:RAG 技术全栈指南》中
教程人工智能大模型RAG
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考