docs(comments): 注释中文化 · internal/runtime/memory(31 文件,纯替换)
- 只翻注释/文档字符串:820 增 / 820 删 严格 1:1;剥掉整行注释与行尾注释尾巴后, 逐文件与 HEAD 比对「代码部分差异 = 0」;裁决/design 编号计数逐项与 HEAD 一致 - 保留可执行 SQL 样例、SQLite 报错原文、pgvector/HNSW/ANN/BLOB 等技术词原文 - 复核:go build 绿 + memory/projectindex/vector 三包测试绿;gofmt 仅剩 2 个既有未格式化文件(改动前即如此,未顺手修)
This commit is contained in:
1 parent
eb1828ee7a
commit
61cf80d5ee
31 files changed
+820
-820
No files matched your search
@@ -13,54 +13,54 @@ import (
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/memory/vector"
|
||||
)
|
||||
|
||||
// Retriever defaults and tuning constants.
|
||||
// 检索器默认值与可调常量。
|
||||
const (
|
||||
// defaultKeywordWeight is the relative weight of keyword (FTS/TF-IDF)
|
||||
// relevance in the combined score.
|
||||
// defaultKeywordWeight 是关键词(FTS/TF-IDF)
|
||||
// 相关性在综合得分中的相对权重。
|
||||
defaultKeywordWeight = 1.0
|
||||
|
||||
// defaultSemanticWeight is the relative weight of semantic-vector relevance
|
||||
// in the combined score. It only contributes when an embedder is configured.
|
||||
// defaultSemanticWeight 是语义向量相关性
|
||||
// 在综合得分中的相对权重。仅在配置了 embedder 时才参与计算。
|
||||
defaultSemanticWeight = 1.0
|
||||
|
||||
// defaultRecencyHalfLifeDays is the number of days over which a memory's
|
||||
// recency weight halves, used to favor recently touched memories (design §2
|
||||
// attenuation).
|
||||
// defaultRecencyHalfLifeDays 是记忆的
|
||||
// 新近度权重减半所需的天数,用于偏好近期被触碰的记忆(design §2
|
||||
// 衰减)。
|
||||
defaultRecencyHalfLifeDays = 30.0
|
||||
|
||||
// defaultTopK is the number of results returned when Retrieve or Inject is
|
||||
// called with a non-positive topK.
|
||||
// defaultTopK 是 Retrieve 或 Inject 以
|
||||
// 非正 topK 调用时返回的结果数。
|
||||
defaultTopK = 10
|
||||
|
||||
// hoursPerDay converts a day count into a time.Duration.
|
||||
// hoursPerDay 把天数换算为 time.Duration。
|
||||
hoursPerDay = 24
|
||||
)
|
||||
|
||||
// Result is a single retrieval hit: the memory item plus its combined
|
||||
// relevance × decay score explaining its ranking. Higher is more relevant.
|
||||
// Result 是一次检索命中:记忆条目,外加解释其排名的
|
||||
// 相关性 × 衰减综合得分。得分越高越相关。
|
||||
type Result struct {
|
||||
Item MemoryItem
|
||||
Score float64
|
||||
// SourceConversation is the conversation the item was extracted from, for
|
||||
// 溯源 (design §2). 裁决 04.4.3 起为结构化 JSON(session_id/message_id,
|
||||
// 见 ParseSourceConversation),由持久化层盖真实 id。It mirrors
|
||||
// Item.SourceConversation so callers can trace a hit without reaching into
|
||||
// Item.
|
||||
// SourceConversation 是条目提取自哪次会话,用于
|
||||
// 溯源(design §2)。裁决 04.4.3 起为结构化 JSON(session_id/message_id,
|
||||
// 见 ParseSourceConversation),由持久化层盖真实 id。它是
|
||||
// Item.SourceConversation 的镜像,调用方无需深入
|
||||
// Item 即可回溯一次命中。
|
||||
SourceConversation string
|
||||
// Project is the linked project-index entry (设计 §3 项目分层), populated only
|
||||
// when the retriever is wired with a project-index store and the item is a
|
||||
// project-scoped memory carrying a ProjectID. It lets callers surface the
|
||||
// 项目索引 → 项目描述 → 章节内容 chain for a retrieved item.
|
||||
// Project 是链接的项目索引条目(设计 §3 项目分层),仅当
|
||||
// 检索器接入了项目索引存储、且该条目是携带 ProjectID 的
|
||||
// 项目作用域记忆时才填充。它让调用方为一条检索结果
|
||||
// 呈现「项目索引 → 项目描述 → 章节内容」链路。
|
||||
Project *projectindex.Project
|
||||
}
|
||||
|
||||
// Retriever ranks the long-term memories visible to a persona — its own items
|
||||
// plus the shared user/project scopes (design §3/§4, see visibleCandidates) —
|
||||
// by relevance (keyword and, when an embedder or vector index is configured,
|
||||
// semantic vectors) weighted by decay (decay_weight × time decay), then returns
|
||||
// the top K. It is stateless and safe for concurrent use; the ranking is
|
||||
// recomputed from the store on every call, so results always reflect persisted
|
||||
// state.
|
||||
// Retriever 对某角色可见的长期记忆 —— 它自己的条目
|
||||
// 加共享的用户/项目作用域(design §3/§4,见 visibleCandidates)——
|
||||
// 按相关性(关键词,以及配置了 embedder 或向量索引时的
|
||||
// 语义向量)加权衰减(decay_weight × 时间衰减)排序后返回
|
||||
// 前 K 条。它是无状态的、并发安全;排名在
|
||||
// 每次调用时从存储重算,因此结果始终反映持久化的
|
||||
// 状态。
|
||||
type Retriever struct {
|
||||
store *Store
|
||||
embedder embedding.Embedder
|
||||
@@ -71,43 +71,43 @@ type Retriever struct {
|
||||
recencyHalfLife time.Duration
|
||||
}
|
||||
|
||||
// RetrieverOption customizes a Retriever built by NewRetriever.
|
||||
// RetrieverOption 定制由 NewRetriever 构建的 Retriever。
|
||||
type RetrieverOption func(*Retriever)
|
||||
|
||||
// WithEmbedder sets the semantic embedder used for vector similarity computed
|
||||
// in-memory. When nil (the default), retrieval falls back to keyword matching
|
||||
// plus decay only. When a vector index is also wired via WithVectorIndex, the
|
||||
// index takes precedence: it is the persistent semantic path (design §3
|
||||
// 可选增强), and WithEmbedder computes similarity on the fly instead.
|
||||
// WithEmbedder 设置用于内存内向量相似度计算的语义 embedder。
|
||||
// 为 nil(默认)时,检索退化为仅关键词匹配
|
||||
// 加衰减。若同时经 WithVectorIndex 接入了向量索引,则
|
||||
// 索引优先:它是持久化语义路径(design §3
|
||||
// 可选增强),此时 WithEmbedder 改为即时计算相似度。
|
||||
func WithEmbedder(e embedding.Embedder) RetrieverOption {
|
||||
return func(r *Retriever) {
|
||||
r.embedder = e
|
||||
}
|
||||
}
|
||||
|
||||
// WithVectorIndex wires a persistent vector.Index into retrieval. When set,
|
||||
// semantic relevance is answered by idx.Search over the stored vectors rather
|
||||
// than by in-memory embedding, so a configured pgvector/SQLite vector store is
|
||||
// genuinely used (design §3 "真正启用向量检索"). When nil (the default), the
|
||||
// semantic path is skipped unless WithEmbedder is set. The caller is responsible
|
||||
// for keeping the index in sync with stored items (embed + add on write).
|
||||
// WithVectorIndex 把持久化的 vector.Index 接入检索。设置后,
|
||||
// 语义相关性由 idx.Search 在已存向量上作答,而非
|
||||
// 内存内向量化,因此配置的 pgvector/SQLite 向量存储被
|
||||
// 真正启用(design §3「真正启用向量检索」)。为 nil(默认)时,
|
||||
// 除非设置了 WithEmbedder,否则语义路径跳过。调用方负责
|
||||
// 让索引与已存条目保持同步(写入时 embed + add)。
|
||||
func WithVectorIndex(idx vector.Index) RetrieverOption {
|
||||
return func(r *Retriever) {
|
||||
r.vindex = idx
|
||||
}
|
||||
}
|
||||
|
||||
// WithProjectIndex wires a projectindex.Store so retrieval can attach the linked
|
||||
// project-index entry (设计 §3 项目分层) to each project-scoped hit. When nil
|
||||
// (the default), Result.Project is left unset.
|
||||
// WithProjectIndex 接入 projectindex.Store,使检索能把链接的
|
||||
// 项目索引条目(设计 §3 项目分层)附加到每条项目作用域命中上。为 nil
|
||||
// (默认)时,Result.Project 不设置。
|
||||
func WithProjectIndex(s *projectindex.Store) RetrieverOption {
|
||||
return func(r *Retriever) {
|
||||
r.projectStore = s
|
||||
}
|
||||
}
|
||||
|
||||
// WithKeywordWeight sets the relative weight of keyword relevance. Non-positive
|
||||
// values are ignored and the default kept.
|
||||
// WithKeywordWeight 设置关键词相关性的相对权重。非正值
|
||||
// 被忽略,保留默认值。
|
||||
func WithKeywordWeight(w float64) RetrieverOption {
|
||||
return func(r *Retriever) {
|
||||
if w > 0 {
|
||||
@@ -116,8 +116,8 @@ func WithKeywordWeight(w float64) RetrieverOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithSemanticWeight sets the relative weight of semantic relevance. Non-positive
|
||||
// values are ignored and the default kept.
|
||||
// WithSemanticWeight 设置语义相关性的相对权重。非正值
|
||||
// 被忽略,保留默认值。
|
||||
func WithSemanticWeight(w float64) RetrieverOption {
|
||||
return func(r *Retriever) {
|
||||
if w > 0 {
|
||||
@@ -126,8 +126,8 @@ func WithSemanticWeight(w float64) RetrieverOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithRecencyHalfLife sets how many days a memory's recency weight takes to
|
||||
// halve. Non-positive values are ignored and the default kept.
|
||||
// WithRecencyHalfLife 设置记忆的新近度权重减半所需的天数。
|
||||
// 非正值被忽略,保留默认值。
|
||||
func WithRecencyHalfLife(days float64) RetrieverOption {
|
||||
return func(r *Retriever) {
|
||||
if days > 0 {
|
||||
@@ -136,8 +136,8 @@ func WithRecencyHalfLife(days float64) RetrieverOption {
|
||||
}
|
||||
}
|
||||
|
||||
// NewRetriever builds a Retriever over store. A nil store is invalid and panics
|
||||
// at call time; callers should pass a non-nil store.
|
||||
// NewRetriever 在 store 之上构建 Retriever。nil store 非法,会在
|
||||
// 调用时 panic;调用方应传入非 nil 的 store。
|
||||
func NewRetriever(store *Store, opts ...RetrieverOption) *Retriever {
|
||||
r := &Retriever{
|
||||
store: store,
|
||||
@@ -151,15 +151,15 @@ func NewRetriever(store *Store, opts ...RetrieverOption) *Retriever {
|
||||
return r
|
||||
}
|
||||
|
||||
// Retrieve returns up to topK items for personaID that best match query, ranked
|
||||
// by relevance (keyword + optional semantic) weighted by decay (decay_weight ×
|
||||
// time decay). 候选集是 design/03 §3/§4 的可见域并集:该角色自己的条目
|
||||
// Retrieve 为 personaID 返回最匹配 query 的最多 topK 条,按
|
||||
// 相关性(关键词 + 可选语义)加权衰减(decay_weight ×
|
||||
// 时间衰减)排序。候选集是 design/03 §3/§4 的可见域并集:该角色自己的条目
|
||||
// (ListByPersona)∪ 全局共享的用户习惯域(ScopeUser)∪ 人人可读的
|
||||
// 项目域(ScopeProject),按 id 去重。他角色的角色日常记忆(ScopePersona)
|
||||
// 绝不进入候选集 —— 跨角色读只走「把 B 当子代理去查 B」的派发机制
|
||||
// (design/22 §9),直接检索不越权。Query and candidate contents are both
|
||||
// read under ctx; a canceled context aborts the work. A blank query returns the
|
||||
// most recently used items (decay order only).
|
||||
// (design/22 §9),直接检索不越权。query 与候选内容都在
|
||||
// ctx 下读取;ctx 被取消会中止工作。query 为空时返回
|
||||
// 最近使用过的条目(仅按衰减顺序)。
|
||||
func (r *Retriever) Retrieve(ctx context.Context, personaID, query string, topK int) ([]Result, error) {
|
||||
if topK <= 0 {
|
||||
topK = defaultTopK
|
||||
@@ -176,10 +176,10 @@ func (r *Retriever) Retrieve(ctx context.Context, personaID, query string, topK
|
||||
sem := r.semanticScores(ctx, items, query)
|
||||
|
||||
now := time.Now().UTC()
|
||||
// matched is true when at least one item shares relevance with the query. In
|
||||
// that mode relevance dominates ranking and recency only attenuates among
|
||||
// matches; unmatched items get score 0 and sort below. When nothing matches
|
||||
// (e.g. a blank query), we rank by recency alone.
|
||||
// matched 为 true 表示至少有一条条目与 query 有相关性。这种
|
||||
// 模式下相关性主导排名,新近度只在命中项之间
|
||||
// 衰减;未命中的条目得分为 0 且排在后面。无任何命中
|
||||
// (例如 query 为空)时,只按新近度排名。
|
||||
matched := false
|
||||
relevance := make(map[string]float64, len(items))
|
||||
for _, it := range items {
|
||||
@@ -255,10 +255,10 @@ func (r *Retriever) visibleCandidates(ctx context.Context, personaID string) ([]
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// keywordScores scores each item against query with an in-memory TF-IDF inverted
|
||||
// index (embedding.Index), reusing the embedding package's tokenizer. Scores are
|
||||
// normalized to [0,1] by the maximum so they are comparable with the semantic
|
||||
// scores.
|
||||
// keywordScores 用内存内 TF-IDF 倒排索引(embedding.Index)为每条条目
|
||||
// 与 query 打分,复用 embedding 包的分词器。得分
|
||||
// 以最大值为基准归一化到 [0,1],以便与语义
|
||||
// 得分可比。
|
||||
func (r *Retriever) keywordScores(items []MemoryItem, query string) map[string]float64 {
|
||||
scores := make(map[string]float64, len(items))
|
||||
index := embedding.NewIndex[string]()
|
||||
@@ -279,12 +279,12 @@ func (r *Retriever) keywordScores(items []MemoryItem, query string) map[string]f
|
||||
return scores
|
||||
}
|
||||
|
||||
// semanticScores computes per-item semantic relevance for query. When a vector
|
||||
// index is wired (WithVectorIndex) it answers from the persistent index via
|
||||
// idx.Search — the real vector path (design §3). Otherwise, with an embedder
|
||||
// configured, it embeds query and candidates in-memory and uses clamped cosine
|
||||
// similarity. With neither it returns an empty map, so semantic relevance
|
||||
// contributes nothing.
|
||||
// semanticScores 计算每个条目对 query 的语义相关性。接入了向量
|
||||
// 索引(WithVectorIndex)时,它经 idx.Search 从持久化索引
|
||||
// 作答 —— 真正的向量路径(design §3)。否则,在配置了 embedder
|
||||
// 时,它在内存内对 query 与候选做向量化并使用夹取的余弦
|
||||
// 相似度。两者都没有时返回空 map,因此语义相关性
|
||||
// 不贡献任何分数。
|
||||
func (r *Retriever) semanticScores(ctx context.Context, items []MemoryItem, query string) map[string]float64 {
|
||||
scores := make(map[string]float64, len(items))
|
||||
if r.vindex != nil {
|
||||
@@ -319,11 +319,11 @@ func (r *Retriever) semanticScores(ctx context.Context, items []MemoryItem, quer
|
||||
return scores
|
||||
}
|
||||
|
||||
// vectorIndexScores answers semantic relevance from the persistent vector index:
|
||||
// it embeds the query with the index, searches for the top similar ids, and
|
||||
// returns their cosine similarity, normalized to [0,1] by the top hit. Because
|
||||
// idx.Search ranks over all stored ids, an item absent from the index carries no
|
||||
// semantic score. A blank query or an index error degrades to an empty map.
|
||||
// vectorIndexScores 从持久化向量索引作答语义相关性:
|
||||
// 它用索引对 query 做向量化,检索最相似的若干 id,并
|
||||
// 返回它们的余弦相似度,以最高命中为基准归一化到 [0,1]。由于
|
||||
// idx.Search 在全部已存 id 上排名,不在索引中的条目没有
|
||||
// 语义得分。query 为空或索引报错时退化为空 map。
|
||||
func (r *Retriever) vectorIndexScores(ctx context.Context, items []MemoryItem, query string) map[string]float64 {
|
||||
scores := make(map[string]float64, len(items))
|
||||
if strings.TrimSpace(query) == "" {
|
||||
@@ -348,10 +348,10 @@ func (r *Retriever) vectorIndexScores(ctx context.Context, items []MemoryItem, q
|
||||
return scores
|
||||
}
|
||||
|
||||
// projectFor returns the project-index entry linked to a project-scoped item, or
|
||||
// nil when the retriever has no project-index store, the item is not
|
||||
// project-scoped, or it carries no ProjectID. A load failure is treated as "no
|
||||
// project" rather than failing the whole retrieval (design §3 项目索引各角色可查).
|
||||
// projectFor 返回链接到项目作用域条目的项目索引条目;检索器
|
||||
// 没有项目索引存储、条目不是
|
||||
// 项目作用域、或条目不带 ProjectID 时返回 nil。加载失败按「无
|
||||
// 项目」处理,而不是让整次检索失败(design §3 项目索引各角色可查)。
|
||||
func (r *Retriever) projectFor(ctx context.Context, it MemoryItem) *projectindex.Project {
|
||||
if r.projectStore == nil || it.Scope != ScopeProject || strings.TrimSpace(it.ProjectID) == "" {
|
||||
return nil
|
||||
@@ -363,7 +363,7 @@ func (r *Retriever) projectFor(ctx context.Context, it MemoryItem) *projectindex
|
||||
return p
|
||||
}
|
||||
|
||||
// keywordText is the text indexed for keyword matching: content plus tags.
|
||||
// keywordText 是用于关键词匹配的索引文本:content 加 tags。
|
||||
func keywordText(it MemoryItem) string {
|
||||
if len(it.Tags) == 0 {
|
||||
return it.Content
|
||||
@@ -371,8 +371,8 @@ func keywordText(it MemoryItem) string {
|
||||
return it.Content + " " + strings.Join(it.Tags, " ")
|
||||
}
|
||||
|
||||
// cosineSim computes the cosine similarity of two vectors, clamped to [0,1] by
|
||||
// the caller. Zero-length or mismatched vectors yield 0.
|
||||
// cosineSim 计算两个向量的余弦相似度,由调用方夹到 [0,1]。
|
||||
// 长度为零或不一致的向量得 0。
|
||||
func cosineSim(a, b embedding.Vector) float64 {
|
||||
if len(a) == 0 || len(b) == 0 || len(a) != len(b) {
|
||||
return 0
|
||||
@@ -389,10 +389,10 @@ func cosineSim(a, b embedding.Vector) float64 {
|
||||
return dot / (math.Sqrt(na) * math.Sqrt(nb))
|
||||
}
|
||||
|
||||
// Inject retrieves up to topK relevant memories for personaID and formats them
|
||||
// as a compact context block ready to be injected into the prompt. It returns an
|
||||
// empty string when nothing is retrieved, so callers can ignore the result when
|
||||
// there is no relevant memory.
|
||||
// Inject 为 personaID 检索最多 topK 条相关记忆,并把它们格式化为
|
||||
// 紧凑的上下文块,可供注入提示词。没有检索到任何内容时
|
||||
// 返回空字符串,因此没有相关记忆时调用方可以忽略
|
||||
// 结果。
|
||||
func (r *Retriever) Inject(ctx context.Context, personaID, query string, topK int) (string, error) {
|
||||
results, err := r.Retrieve(ctx, personaID, query, topK)
|
||||
if err != nil {
|
||||
|
||||
Reference in new issue
Block a user