RAG 和 agent 记忆通常意味着三套系统:一个缓存、一个向量数据库、一个搜索索引——同样的事实存三份,然后慢慢彼此对不上。kevy 把向量 KNN、BM25 全文检索和变更流都放在引擎里,直接建在你已经写进去的那些 key 上。
RAG 这一套里,贵的不是检索,而是让三份事实保持同步:你写了一篇文档,然后你还得记住去做 embedding、去建索引、去让缓存失效。这里每一步都是一个可能忘掉的地方。
在 kevy 里,索引是一句声明,不是一条流水线。你告诉引擎是哪些 key、哪个字段,写路径就会把索引维持在最新。事后没有东西要跑,也没有东西会落后。
kevy 不做的那件事,是生成 embedding。引擎里没有模型,以后也不会有——推理不该待在存储引擎里,硬塞进去只会把你的向量格式绑死在我们的发版节奏上。向量由你给出;kevy 负责存它、索引它、检索它。
在你已经在写的那些 key 的某个字段上做 KNN。声明一次,写路径把它维持在最新,没有东西要同步。
IDX.CREATE idx:sem ON PREFIX doc: FIELD vec TYPE vector KIND ann DIM 768 DISTANCE cosine M 16 EF 200
-> OK引擎会回填已有的 key,回填期间回答 INDEXBUILDING。
HSET doc:4410 title "Ada on pipelining" vec "<768 f32, little-endian>"IDX.QUERY idx:sem KNN "<query vector>" LIMIT 10
-> 1) doc:4410
2) doc:9982成本与限制 这个索引是 HNSW,是近似的:召回率是一个可调参数(EF),不是一个保证。第一次构建是对匹配到的 key 做 O(N) 的工作——要提前安排,不要等它自己撞上来。还有,这里没有 embedding 模型:向量由你给出。向量检索指南里有那些可以调的参数。
在同一批 key 上做 BM25,再用一条混合查询,把文本排序和向量排序融合在一条命令里。
IDX.CREATE idx:ft ON PREFIX doc: FIELD title TYPE str KIND text
-> OKIDX.QUERY idx:ft MATCH "pipelining"
-> 1) 1) "doc:1"
2) "0.2877" # the BM25 scoreIDX.QUERY HYBRID idx:ft MATCH "pipelining" idx:sem KNN "<vector>" LIMIT 20 RRFK 60成本与限制 索引的账是在每次写匹配到的 key 时付的——对读多的检索这是对的交易,对一个每秒重写几千次的 key 是错的。分词(包括中日韩)和 BM25 到哪里为止,都在全文检索指南里。
从另一个进程持续跟读每一次写——变更时才做 embedding,不靠时刻表,还能从停下的地方续上。
# kevy.toml
[feed]
enabled = trueFEED.SHARDS -> (integer) 16
FEED.TAIL 0 -> 1) (integer) 1 # generation
2) (integer) 1 # offsetFEED.READ 0 1 0 COUNT 2 -> the writes themselves, replayable成本与限制 变更流是按分片记的:FEED.SHARDS 告诉你手里有几个游标,你的消费者按分片各记一个偏移量。它默认是关的——打开 [feed] 才买下写路径上的这份记账。变更流指南讲了怎么跨重启续读。
llms-full.txt 一次抓取就够:每条命令的真实代价、相对 Redis 的真实偏差,加上每一篇指南的完整正文。它是从引擎自己的命令表生成的,所以不会和服务端的实际行为脱节。