// 每 user 物理分片的余弦近邻索引(暴力扫描 + 全量排序取 top-k)。
// 分片即隔离:任何检索必须显式携带 user_id。
///|
/// 向量索引:user_id → (entry_id → 向量)。
/// Package-private (abstract): host API is MemoryStore only.
struct VectorIndex {
shards : Map[String, Map[String, Array[Double]]]
}
///|
fn VectorIndex::new() -> VectorIndex {
VectorIndex::{ shards: Map([]), }
}
///|
fn VectorIndex::shard(
self : VectorIndex,
user_id : String,
) -> Map[String, Array[Double]] {
match self.shards.get(user_id) {
Some(m) => m
None => {
let m : Map[String, Array[Double]] = Map([])
self.shards.set(user_id, m)
m
}
}
}
///|
/// 写入 / 覆盖某 user 的一条向量。
fn VectorIndex::upsert(
self : VectorIndex,
user_id : String,
id : String,
vec : Array[Double],
) -> Unit {
self.shard(user_id).set(id, vec)
}
///|
/// 摘除某 user 的一条向量(supersede / forget 共用)。
fn VectorIndex::remove(
self : VectorIndex,
user_id : String,
id : String,
) -> Unit {
match self.shards.get(user_id) {
Some(m) => m.remove(id)
None => ()
}
}
///|
/// 某 user 分片的条目数(wbtest 用;主路径不读 size)。
#warnings("-unused_value")
fn VectorIndex::size(self : VectorIndex, user_id : String) -> Int32 {
match self.shards.get(user_id) {
Some(m) => m.length()
None => 0
}
}
///|
/// 余弦近邻检索:仅在该 user 分片内暴力扫描,按相似度降序取前 k。
/// 零相似(正交 / 查询向量为空)不返回,保证"无结果返回空列表"。
/// 排序确定性:相似度降序,相同分按 id 升序。
fn VectorIndex::search(
self : VectorIndex,
user_id : String,
query_vector : Array[Double],
k : Int32,
) -> Array[(String, Double)] {
if query_vector.length() == 0 {
return []
}
let scored : Array[(String, Double)] = []
match self.shards.get(user_id) {
Some(m) =>
for kv in m.iter() {
let score = cosine_similarity(query_vector, kv.1)
if score > 0.0 {
scored.push((kv.0, score))
}
}
None => ()
}
scored.sort_by((a, b) => {
if a.1 > b.1 {
-1
} else if a.1 < b.1 {
1
} else if a.0 < b.0 {
-1
} else if a.0 > b.0 {
1
} else {
0
}
})
let out : Array[(String, Double)] = []
let limit = if k < scored.length() { k } else { scored.length() }
for i = 0; i < limit; i = i + 1 {
out.push(scored[i])
}
out
}