Memory

语义记忆(Semantic Memory) 示例

使用可插拔向量存储 + Embedder 为 aster 增强 RAG 能力

语义记忆(Semantic Memory) 示例

aster 默认采用 文件 + grep 的方式实现长期记忆, 对很多场景(用户/项目/资源级记忆)已经足够简单可靠。但对于部分用户, 仍然希望接入向量数据库/语义检索(RAG)。

本示例展示如何在保持核心运行时轻量的前提下, 通过 可插拔 VectorStore + Embedder 增强语义记忆能力。

注意: 当前版本仅提供内存 VectorStore 和 MockEmbedder 作为示例实现, 方便你理解接口与集成方式。生产环境需自行实现适配器, 对接真实的向量数据库和 embedding 服务。

0. 交互式 Playground

下面的组件可以直接调用 aster serve 暴露的 /v1/memory/semantic/search 接口, 体验语义检索效果:

1. 核心抽象

向量相关抽象定义在 pkg/vector 中:

// pkg/vector/store.go

type Document struct {
    ID        string
    Text      string
    Embedding []float32
    Metadata  map[string]interface{}
    Namespace string
}

type Query struct {
    Vector    []float32
    TopK      int
    Namespace string
    Filter    map[string]interface{}
}

type Hit struct {
    ID       string
    Score    float64
    Metadata map[string]interface{}
}

type VectorStore interface {
    Upsert(ctx context.Context, docs []Document) error
    Delete(ctx context.Context, ids []string) error
    Query(ctx context.Context, q Query) ([]Hit, error)
    Close() error
}

// pkg/vector/embedder.go
type Embedder interface {
    EmbedText(ctx context.Context, texts []string) ([][]float32, error)
}

语义记忆组件定义在 pkg/memory/semantic.go:

type SemanticMemoryConfig struct {
    Store          vector.VectorStore
    Embedder       vector.Embedder
    NamespaceScope string // "user" | "project" | "resource" | "global"
    TopK           int
}

type SemanticMemory struct {
    cfg SemanticMemoryConfig
}

func NewSemanticMemory(cfg SemanticMemoryConfig) *SemanticMemory
func (sm *SemanticMemory) Enabled() bool
func (sm *SemanticMemory) Close() error

// Index 将文本写入向量索引
func (sm *SemanticMemory) Index(ctx context.Context, docID, text string, meta map[string]interface{}) error

// Search 基于自然语言查询 + 元数据执行向量检索
func (sm *SemanticMemory) Search(ctx context.Context, query string, meta map[string]interface{}, topK int) ([]vector.Hit, error)

2. 内存 VectorStore + MockEmbedder 示例

示例代码: examples/memory-semantic/main.go

ctx := context.Background()

// 1. 创建向量存储和 embedder
store := vector.NewMemoryStore()
embedder := vector.NewMockEmbedder(16)

// 2. 创建语义记忆组件
semMem := memory.NewSemanticMemory(memory.SemanticMemoryConfig{
    Store:          store,
    Embedder:       embedder,
    NamespaceScope: "resource",
    TopK:           3,
})

// 3. 索引几段示例文本
docs := []struct {
    id   string
    text string
    meta map[string]interface{}
}{
    {
        id:   "doc-1",
        text: "Paris is the capital of France.",
        meta: map[string]interface{}{"user_id": "alice", "resource_id": "europe-notes"},
    },
    {
        id:   "doc-2",
        text: "Berlin is the capital of Germany.",
        meta: map[string]interface{}{"user_id": "alice", "resource_id": "europe-notes"},
    },
}

for _, d := range docs {
    if err := semMem.Index(ctx, d.id, d.text, d.meta); err != nil {
        panic(err)
    }
}

// 4. 在 Alice 的 europe-notes 命名空间内进行语义检索
query := "What is the capital of France?"
meta := map[string]interface{}{"user_id": "alice", "resource_id": "europe-notes"}

hits, err := semMem.Search(ctx, query, meta, 3)
if err != nil {
    panic(err)
}

fmt.Println("Semantic search hits:", hits)

运行:

cd examples
go run ./memory-semantic

你会看到 doc-1 得分较高, 表明语义检索可以在指定命名空间内找到相关文档。

3. 将语义记忆作为工具暴露给 Agent

pkg/tools/builtin/semantic_memory.go 提供了一个内置工具 semantic_search, 你可以在构建 Agent 时按需注册:

semMem := memory.NewSemanticMemory(...)

toolRegistry := tools.NewRegistry()
builtin.RegisterAll(toolRegistry)

if semMem.Enabled() {
    toolRegistry.Register("semantic_search", func(cfg map[string]interface{}) (tools.Tool, error) {
        return builtin.NewSemanticSearchTool(semMem), nil
    })
}

工具输入:

{
  "query": "自然语言查询文本",
  "top_k": 5,
  "metadata": {
    "user_id": "alice",
    "project_id": "demo"
  }
}

输出为命中列表:

[
  {"id": "doc-1", "score": 0.87, "metadata": {...}},
  {"id": "doc-2", "score": 0.75, "metadata": {...}}
]

这样 Agent 在工具列表中就可以选择使用 semantic_search 进行 RAG 检索, 而无需修改核心运行时。

4. 通过 aster.yaml 启用语义记忆

aster.yaml 中可以声明向量存储、embedder 和语义记忆配置:

vector_stores:
  - name: main
    kind: memory # 内存向量存储, 适合本地测试/示例

embedders:
  - name: default
    kind: mock # mock 实现, 生产环境应替换为真实 embedder

semantic_memory:
  enabled: true
  store: main
  embedder: default
  top_k: 8
  namespace_scope: resource

当你使用 CLI 启动 Server 时:

aster serve \
  --addr :8080 \
  --workspace ./workspace \
  --store .aster \
  --config ./aster.yaml

aster serve 会自动:

  • 根据配置创建 VectorStore(当前为 MemoryStore) 和 Embedder(当前为 MockEmbedder);
  • 构造 SemanticMemory;
  • 注册 semantic_search 工具。

如果配置无效或未提供有效 store/embedder, 语义记忆会被跳过, 不影响现有行为。

使用 pgvector + OpenAI 实现生产级语义记忆(示例)

如果你已经在 PostgreSQL 中安装了 pgvector 扩展, 并希望使用 OpenAI 的 embedding 模型, 可以在 aster.yaml 中这样配置:

vector_stores:
  - name: main
    kind: pgvector
    dsn: postgres://user:password@localhost:5432/aster?sslmode=disable
    table: agent_vectors
    metric: cosine
    dimension: 1536

embedders:
  - name: default
    kind: openai
    model: text-embedding-3-small
    env_api_key: OPENAI_API_KEY

semantic_memory:
  enabled: true
  store: main
  embedder: default
  top_k: 8
  namespace_scope: resource

在数据库中确保创建好表结构(示例):

CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE IF NOT EXISTS agent_vectors (
  id TEXT PRIMARY KEY,
  namespace TEXT,
  embedding VECTOR(1536),
  metadata JSONB
);

启动服务前设置环境变量:

export OPENAI_API_KEY=sk-...
aster serve --config ./aster.yaml

此时 semantic_search 工具会使用 pgvector 存储向量, 使用 OpenAI embeddings 进行查询。

5. 设计对齐与下一步

  • 设计要点:
    • 采用抽象接口(VectorStore/Embedder) + 可插拔 adapter 的模式。
    • 语义记忆是对现有 Memory 的增强层, 而不是硬依赖。
  • 当前限制:
    • 目前仅提供内存向量存储 + mock embedder 示例, 不直接内置任何外部向量数据库或 embedding 服务。
    • 你可以基于这些接口自行实现 PgVector/Qdrant/OpenAI 等适配器, 注入到 SemanticMemory

推荐下一步:

  • 如果你需要生产级 RAG, 建议:
    • 实现一个 vector.VectorStore adapter 对接现有向量数据库(PgVector/LibSQL/Qdrant等)。
    • 实现一个 vector.Embedder adapter 对接 OpenAI/本地 embedding 服务。
    • aster.yaml 中添加相应的 kind 和连接参数。