Skip to content

Vectorize

长期语义记忆。"Margaret 怎么看 Tom?"——靠语义而非关键词来回答。

什么是 Vectorize?

Cloudflare Vectorize 是一个无服务器向量数据库,与 Workers 深度集成。

在 AI World Sim 中,它将 NPC 的长期记忆 存储为向量嵌入。当模拟需要为某个 NPC 召回相关的过往事件时,通过语义相似度进行查询。


为什么需要向量记忆?

D1 按时间顺序存储记忆。要单独用 D1 找到 "Margaret 怎么看 Tom?",就得扫描她的所有日记。

有了 Vectorize:

  1. 嵌入查询:"Margaret 对 Tom 的看法"
  2. 向 Vectorize 查询最相似的 Top-K 条记忆
  3. 瞬间返回语义相关的记忆

这与人类记忆的工作方式一致——我们按意义而非日期来回忆。


索引结构

我们为整个 NPC 记忆语料使用一个 Vectorize 索引,通过元数据按 NPC 过滤。

元数据结构

每个向量携带元数据:

ts
interface VectorMetadata {
  npcId: string;
  type: 'event' | 'relationship' | 'opinion' | 'place';
  day: number;
  importance: number;
  decayFactor: number;
  textPreview: string;
}

嵌入模型

默认使用 OpenAI 的 text-embedding-3-small(1536 维)。也可以使用 Workers AI 的 @cf/baai/bge-base-en-v1.5(768 维)作为免费的本地嵌入方案。


何时进行记忆嵌入

1. 实时嵌入(Tick 过程中)

当一条高重要性的短期记忆被创建(importance > 0.7)时,立即嵌入并写入 Vectorize。

ts
async function indexMemory(memory: ShortTermMemory, env: Env) {
  const embedding = await embed(memory.content, env);
  await env.VECTORIZE.upsert([{
    id: memory.id,
    values: embedding,
    metadata: {
      npcId: memory.npcId,
      type: 'event',
      day: memory.worldTime.day,
      importance: memory.importance,
      decayFactor: 0,
      textPreview: memory.content.slice(0, 200),
    },
  }]);
}

2. 后台整合(每 6 小时)

较旧的每日摘要会被浓缩为 1~2 句话并进行嵌入。


检索 API

简单查询

ts
async function recall(npcId: string, query: string, env: Env, topK = 5) {
  const queryEmbedding = await embed(query, env);

  const results = await env.VECTORIZE.query(queryEmbedding, {
    topK,
    filter: { npcId },
    returnMetadata: true,
  });

  return results.matches.map(m => ({
    text: m.metadata.textPreview,
    importance: m.metadata.importance,
    day: m.metadata.day,
    score: m.score,
  }));
}

加权查询

在决策场景下,会应用衰减权重:

ts
function weightedRecall(matches: Match[], currentDay: number) {
  return matches
    .map(m => ({
      ...m,
      effectiveScore: m.score * m.metadata.importance / (1 + (currentDay - m.metadata.day) * m.metadata.decayFactor),
    }))
    .sort((a, b) => b.effectiveScore - a.effectiveScore);
}

使用场景

1. 决策上下文

NPC 在决定做什么时,会查询相关记忆:

ts
const context = await recall(
  npcId,
  `${npc.name} is deciding whether to go to the pub tonight`,
  env
);

2. 日记生成

写日记时,会调出相关的过往事件。

3. 关系上下文

两个 NPC 互动时,各自回忆与对方的历史。


衰减与清理

记忆不会永存。有两个机制:

衰减因子

每条记忆都有一个 decayFactor,随时间增长。检索时,衰减会降低有效分数。

主动清理

每日任务会查询所有 decayFactor > 0.9 的记忆。对重要的重新嵌入,其余删除。


故障处理

故障处理方式
嵌入 API 失败记忆仍存储在 D1 中;稍后重试嵌入
Vectorize 查询返回空决策模块降级为仅使用短期记忆
索引大小达到上限激进清理 + 较旧记忆仅保留在 D1 中

这里不存储什么

  • ❌ 原始日记文本(在 D1 中)
  • ❌ 短期记忆(在 D1 中)
  • ❌ NPC 当前状态(在 D1 中)
  • ❌ 世界事件(在 D1 中)

只有语义记忆存放在 Vectorize 中。

Released under the MIT License.