RedisES——Retriever的抽象实现 RedisES——Retriever的抽象实现Eino 的 Retriever 抽象就是为了屏蔽底层检索实现Redis 和 ElasticsearchES都可以作为 Retriever 的实现只不过支持方式有所区别。Eino 中 Retriever 的定位Eino 将检索能力统一抽象成了一个接口大致可以理解成typeRetrieverinterface{Retrieve(ctx context.Context,querystring,opts...Option)([]*schema.Document,error)}无论底层是向量数据库Milvus、Qdrant、Chroma搜索引擎ElasticsearchRedis自定义数据库最终都只需要实现这个接口即可。这样在 Chain 或 Agent 中只需要依赖 Retriever而不用关心底层是什么。1. ElasticsearchES 非常适合作为 Retriever。可以有几种方式① BM25 检索User Query │ ▼ Elasticsearch (match / multi_match) │ ▼ Documents这种就是传统全文检索。例如如何部署docker ↓ match: content:如何部署docker② 向量检索ES 8.x 已支持 Dense Vector。流程Embedding ↓ query_vector ↓ KNN Search ↓ TopK DocumentsRetriever 内部Embedding(query) ↓ ES KNN ↓ 返回 Document[]③ Hybrid Search很多 RAG 都采用BM25 Vector Search RRFRetriever 完全可以封装这套逻辑。2. RedisRedis 也完全可以做 Retriever。但这里要区分是哪种 Redis。普通 Redis例如GET HGET SCAN它本身没有真正意义上的语义检索。如果只是key - document那更像 KV Store不属于典型 Retriever。Redis StackRediSearch如果安装了Redis Stack或者RediSearch就支持全文检索Vector SearchHybrid Search例如FT.SEARCH KNN VECTOR那么它完全可以作为 Retriever。流程Embedding ↓ Redis Vector Index ↓ TopK ↓ Document[]3. 在 Eino 中如何使用Eino 不要求 Retriever 一定来自官方。例如可以自己实现ESRetriever RedisRetriever MilvusRetriever PGVectorRetriever QdrantRetriever它们都满足Retrieve() ↓ []DocumentChain 完全不用修改。例如KnowledgeRetriever │ ┌────────┴─────────┐ │ │ ESRetriever MilvusRetriever │ │ ES MilvusAgent 根本不知道底层是谁。4. 官方是否内置了 Redis 和 ES截至目前Eino 官方生态主要内置和维护的是对常见向量数据库、Embedding、LLM 等组件的适配。Elasticsearch 和 Redis 并不是官方长期维护的核心 Retriever 实现更多时候需要你自行实现适配或者基于社区扩展来接入。由于 Eino 的 Retriever 接口非常简单实现一个ESRetriever或RedisRetriever的成本并不高只需要把底层查询结果转换成schema.Document即可。总结检索后端能否作为 Retriever是否适合 RAGElasticsearchBM25√√ElasticsearchVector√√ElasticsearchHybrid√√Redis普通 KV注意可以封装但不属于真正语义检索×Redis Stack / RediSearch√√Milvus√√Qdrant√√如果你是在做企业级 RAG 或智能体项目我更推荐采用“ES 向量库”的双检索架构Hybrid RetrievalES 负责关键词/BM25 检索Milvus 或 Qdrant 负责语义向量检索再通过 RRF、加权融合或重排序Reranker合并结果。这也是目前许多生产系统采用的方案。也就是说要区分抽象Interface**和**具体实现Implementation。第一层Retriever 是抽象Eino 中真正依赖的是 Retriever 接口例如typeRetrieverinterface{Retrieve(ctx context.Context,querystring,opts...Option)([]*schema.Document,error)}对于 Chain、Agent 来说它们只认识Retriever并不知道下面到底是谁。例如Retriever ▲ ┌──────────────┼──────────────┐ │ │ │ MilvusRetriever ESRetriever RedisRetriever │ │ │ Milvus ES Redis StackAgent 调用的永远都是Retrieve(...)所以Retriever 抽象可以对应任意数据库。第二层MilvusRetriever 是具体实现MilvusRetriever 的内部代码一般会直接调用 Milvus SDK例如client.Search(...)它知道collectionvector fieldmetric typetopK例如User Query │ Embedding │ MilvusRetriever │ Milvus SDK │ Milvus Server因此MilvusRetriever 不可能直接去查 ES。因为ES 的 API 是POST /_searchMilvus 是Search()参数完全不同。第三层如果数据库换了怎么办假设今天不用 Milvus而换成QdrantWeaviatePGVectorChroma那么应该Retriever ▲ ┌───────────┴───────────┐ │ │ MilvusRetriever QdrantRetriever │ │ Milvus Qdrant只需要typeQdrantRetrieverstruct{client*qdrant.Client}func(r*QdrantRetriever)Retrieve(...){...}即可。整个 Agent 一行代码都不用改。为什么要这样设计这就是经典的依赖倒置Dependency Inversion。例如以前Agent │ ▼ MilvusAgent 被绑死了。现在Agent │ ▼ Retriever │ ├── Milvus ├── ES ├── Redis ├── PGVector └── QdrantAgent 根本不知道下面是谁。以后替换数据库Milvus ↓ QdrantAgent 完全不用改。Eino 为什么要这样抽象因为一个 Retriever 并不一定对应一个数据库它甚至可以组合多个数据源。例如HybridRetriever │ ┌────┴────┐ │ │ ES Milvus │ │ BM25 Vector Search └────┬────┘ ▼ Rank Fusion ▼ Documents对于 Agent 来说它依然只是docs,err:retriever.Retrieve(ctx,query)它不知道内部实际上调用了ESMilvusReranker甚至可以再加Redis CacheKnowledge GraphMySQL都可以。一句话总结RetrieverEino 定义的抽象接口可以适配任意检索后端Milvus、Qdrant、ES、Redis Stack、PGVector 等。MilvusRetrieverRetriever的一个具体实现只负责调用 Milvus因此不能直接用于其他向量数据库。如果更换向量数据库应实现对应的QdrantRetriever、PGVectorRetriever等只要都实现Retriever接口Agent 和 Chain 无需修改。这也是 Eino 将组件分为Loader → Transformer → Indexer → Retriever → Reranker的核心思想上层依赖统一抽象下层可以自由替换具体实现。