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
@@ -10,9 +10,9 @@ import (
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
|
||||
)
|
||||
|
||||
// extractSystemPrompt tells the model.Adapter how to produce candidate memories:
|
||||
// a JSON array of objects, one per memory worth persisting. Format is locked so
|
||||
// parseCandidates can unmarshal it deterministically. 溯源(source_conversation)
|
||||
// extractSystemPrompt 告诉 model.Adapter 如何产出候选记忆:
|
||||
// 一个 JSON 对象数组,每个对象一条值得持久化的记忆。格式固定,
|
||||
// 以便 parseCandidates 确定性地反序列化。溯源(source_conversation)
|
||||
// 刻意不在其中:它由持久化层盖真实 session/message id,绝不由模型生成
|
||||
// (裁决 04.4.3,见 source.go)。
|
||||
const extractSystemPrompt = `你是记忆提取器。从对话中提取值得长期记住的记忆项(用户偏好、习惯、约定、重要事件)。
|
||||
@@ -20,9 +20,9 @@ const extractSystemPrompt = `你是记忆提取器。从对话中提取值得长
|
||||
{"content":"记忆内容","scope":"user|persona|project","kind":"relationship|world_event","tags":["标签"],"confidence":0.9,"project_id":"可选项目id"}
|
||||
去噪去重:只保留值得长期记住的,忽略寒暄与临时信息。`
|
||||
|
||||
// llmCandidate is the wire shape the extractor asks the LLM to emit: one JSON
|
||||
// object per candidate memory. Scope/Kind default to the extractor's configured
|
||||
// values when the model does not provide a valid one. 不含 source_conversation:
|
||||
// llmCandidate 是提取器要求 LLM 输出的线上形状:每条候选
|
||||
// 记忆一个 JSON 对象。模型未给出合法值时,Scope/Kind 取提取器配置的
|
||||
// 默认值。不含 source_conversation:
|
||||
// 模型即使回填该键也会被忽略(结构化溯源只来自持久化层)。
|
||||
type llmCandidate struct {
|
||||
Content string `json:"content"`
|
||||
@@ -33,14 +33,14 @@ type llmCandidate struct {
|
||||
ProjectID string `json:"project_id"`
|
||||
}
|
||||
|
||||
// LLMExtractor implements Extractor by asking a model.Adapter to derive candidate
|
||||
// memory items from a conversation (design §1 LLM 异步自动提取). It runs the LLM
|
||||
// call synchronously in Extract and on a background goroutine in ExtractAsync.
|
||||
// LLMExtractor 让 model.Adapter 从对话中推导候选记忆条目
|
||||
// 来实现 Extractor(design §1 LLM 异步自动提取)。它把 LLM
|
||||
// 调用在 Extract 中同步执行,在 ExtractAsync 中放到后台 goroutine 执行。
|
||||
//
|
||||
// It is idempotent and safe for concurrent use: candidate content is normalized
|
||||
// and deduplicated against a mutex-guarded seen set, so re-extracting the same
|
||||
// memory never yields a second copy, and the same content appearing twice inside
|
||||
// one conversation collapses to a single item.
|
||||
// 它是幂等且并发安全的:候选内容会规范化,
|
||||
// 并在互斥锁保护的 seen 集合上去重,因此对同一条
|
||||
// 记忆重复提取不会产生第二份,同一对话中两次出现的
|
||||
// 相同内容会合并为一条。
|
||||
type LLMExtractor struct {
|
||||
adapter model.Adapter
|
||||
personaID string
|
||||
@@ -52,15 +52,15 @@ type LLMExtractor struct {
|
||||
seen map[string]struct{}
|
||||
}
|
||||
|
||||
// NewLLMExtractor builds an LLMExtractor whose extracted items default to
|
||||
// ScopePersona + KindRelationship when the model does not specify otherwise, and
|
||||
// carry personaID/roleID as their author. The persona is a *default*, not the
|
||||
// authority: a multi-role caller (the runtime loop, design/22 §4.2) builds the
|
||||
// extractor with a blank persona and stamps each item with the role that produced
|
||||
// the turn, so pass "" for a role-agnostic extractor. A nil adapter is rejected at
|
||||
// use time via ErrNilAdapter, matching the lazy construction pattern of the rest
|
||||
// of the runtime; callers may pass nil to build the struct and wire the adapter
|
||||
// later.
|
||||
// NewLLMExtractor 构建 LLMExtractor,当模型未另行指定时,其提取出的条目默认为
|
||||
// ScopePersona + KindRelationship,并以
|
||||
// personaID/roleID 为作者。角色是*默认值*,不是
|
||||
// 权威:多角色调用方(运行时循环,design/22 §4.2)以空
|
||||
// 角色构建提取器,并在持久化时用产出该轮对话的角色给每条条目
|
||||
// 盖章,因此要构建与角色无关的提取器就传 ""。nil adapter 在
|
||||
// 使用期经 ErrNilAdapter 拒绝,与运行时其余部分的
|
||||
// 惰性构造模式一致;调用方可先传 nil 构建结构体、之后再接上
|
||||
// adapter。
|
||||
func NewLLMExtractor(adapter model.Adapter, personaID, roleID string) *LLMExtractor {
|
||||
return &LLMExtractor{
|
||||
adapter: adapter,
|
||||
@@ -72,10 +72,10 @@ func NewLLMExtractor(adapter model.Adapter, personaID, roleID string) *LLMExtrac
|
||||
}
|
||||
}
|
||||
|
||||
// Extract returns the candidate memory items the adapter derives from
|
||||
// conversation. A nil adapter returns ErrNilAdapter; a canceled context aborts
|
||||
// before the LLM call. The response is parsed as a JSON array and then
|
||||
// deduplicated, so items already extracted by this extractor are dropped.
|
||||
// Extract 返回 adapter 从 conversation 推导出的候选记忆条目。
|
||||
// nil adapter 返回 ErrNilAdapter;ctx 被取消会在 LLM 调用前
|
||||
// 中止。响应按 JSON 数组解析,然后
|
||||
// 去重,因此本提取器已提取过的条目会被丢弃。
|
||||
func (e *LLMExtractor) Extract(ctx context.Context, conversation []model.Message) ([]MemoryItem, error) {
|
||||
if e.adapter == nil {
|
||||
return nil, ErrNilAdapter
|
||||
@@ -97,19 +97,19 @@ func (e *LLMExtractor) Extract(ctx context.Context, conversation []model.Message
|
||||
return e.dedupeAndBuild(cands), nil
|
||||
}
|
||||
|
||||
// AsyncResult is the outcome of an ExtractAsync call: either candidate items or
|
||||
// the error that aborted extraction.
|
||||
// AsyncResult 是一次 ExtractAsync 调用的结果:候选条目
|
||||
// 或中止提取的错误。
|
||||
type AsyncResult struct {
|
||||
Items []MemoryItem
|
||||
Err error
|
||||
}
|
||||
|
||||
// ExtractAsync submits the conversation to a background goroutine and returns a
|
||||
// channel that delivers exactly one AsyncResult when extraction finishes, closed
|
||||
// by the goroutine. A nil adapter returns ErrNilAdapter immediately. If ctx is
|
||||
// canceled before the goroutine runs, the result carries the cancellation error.
|
||||
// Because Extract is idempotent, repeated async extraction of the same memory is
|
||||
// safe and never double-records.
|
||||
// ExtractAsync 把对话提交给后台 goroutine,并返回一个
|
||||
// channel:提取完成时投递恰好一条 AsyncResult,随后由该 goroutine
|
||||
// 关闭。nil adapter 立即返回 ErrNilAdapter。若 ctx 在 goroutine 运行前
|
||||
// 被取消,结果携带取消错误。
|
||||
// 由于 Extract 幂等,对同一条记忆重复异步提取是
|
||||
// 安全的,绝不会双写。
|
||||
func (e *LLMExtractor) ExtractAsync(ctx context.Context, conversation []model.Message) (<-chan AsyncResult, error) {
|
||||
if e.adapter == nil {
|
||||
return nil, ErrNilAdapter
|
||||
@@ -127,8 +127,8 @@ func (e *LLMExtractor) ExtractAsync(ctx context.Context, conversation []model.Me
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// buildExtractionPrompt turns the conversation into the model request messages:
|
||||
// the system instruction plus one user message carrying the transcript.
|
||||
// buildExtractionPrompt 把对话转成模型请求消息:
|
||||
// 系统指令,外加一条携带对话记录的用户消息。
|
||||
func buildExtractionPrompt(conversation []model.Message) []model.Message {
|
||||
var b strings.Builder
|
||||
for _, m := range conversation {
|
||||
@@ -143,10 +143,10 @@ func buildExtractionPrompt(conversation []model.Message) []model.Message {
|
||||
}
|
||||
}
|
||||
|
||||
// parseCandidates decodes the adapter's text response into candidates. A fenced
|
||||
// ```json block is unwrapped so the model may wrap its array in markdown. Blank
|
||||
// or non-array content yields no candidates; malformed JSON is an error so the
|
||||
// caller can decide whether to retry or fall back.
|
||||
// parseCandidates 把 adapter 的文本响应解码为候选。被围栏包裹的
|
||||
// ```json 块会被剥离,模型因此可以用 markdown 包住数组。空
|
||||
// 或非数组内容不产出候选;JSON 格式错误则报错,
|
||||
// 由调用方决定重试还是回退。
|
||||
func parseCandidates(s string) ([]llmCandidate, error) {
|
||||
s = strings.TrimSpace(s)
|
||||
if strings.HasPrefix(s, "```") {
|
||||
@@ -172,17 +172,17 @@ func parseCandidates(s string) ([]llmCandidate, error) {
|
||||
return cands, nil
|
||||
}
|
||||
|
||||
// dedupeAndBuild converts valid candidates to MemoryItems, dropping blank content
|
||||
// and any content already emitted by this extractor (idempotent dedup). It
|
||||
// requires e.mu; only valid items mark their dedup key so a malformed candidate
|
||||
// does not suppress a later valid one with the same text.
|
||||
// dedupeAndBuild 把合法候选转成 MemoryItem,丢弃空内容
|
||||
// 以及本提取器已产出的内容(幂等去重)。它
|
||||
// 要求持有 e.mu;只有合法条目才标记其去重键,因此格式错误的候选
|
||||
// 不会压掉之后内容相同的合法候选。
|
||||
//
|
||||
// The persona is NOT required here even though MemoryItem.validate demands one: a
|
||||
// role-agnostic extractor (personaID == "") is legitimate — the caller that knows
|
||||
// the turn's role (the loop) stamps the persona when it persists the item — and
|
||||
// rejecting the item here would silently drop every memory of a multi-role
|
||||
// process. Content/scope/kind, which only this package can judge, are still
|
||||
// enforced. SourceConversation 同理刻意留空:由提取调用点(持久化层)
|
||||
// 这里不要求 persona,尽管 MemoryItem.validate 要求一个:
|
||||
// 与角色无关的提取器(personaID == "")是合法的 —— 知道
|
||||
// 该轮角色的调用方(循环)在持久化条目时盖上角色 —— 而
|
||||
// 在此拒绝条目会静默丢掉多角色
|
||||
// 进程的每一条记忆。只有本包能判断的
|
||||
// content/scope/kind 仍会施加。SourceConversation 同理刻意留空:由提取调用点(持久化层)
|
||||
// 盖真实 session/message id(裁决 04.4.3,见 source.go)。
|
||||
func (e *LLMExtractor) dedupeAndBuild(cands []llmCandidate) []MemoryItem {
|
||||
e.mu.Lock()
|
||||
@@ -217,13 +217,13 @@ func (e *LLMExtractor) dedupeAndBuild(cands []llmCandidate) []MemoryItem {
|
||||
return out
|
||||
}
|
||||
|
||||
// dedupeKey normalizes content for idempotent dedup: case-folded and whitespace-
|
||||
// collapsed, so "记住 A" and "记住 a" collide into one memory.
|
||||
// dedupeKey 为幂等去重规范化内容:大小写折叠并
|
||||
// 压缩空白,因此「记住 A」与「记住 a」会归并为同一条记忆。
|
||||
func dedupeKey(content string) string {
|
||||
return strings.Join(strings.Fields(strings.ToLower(content)), " ")
|
||||
}
|
||||
|
||||
// normalizeScope returns s when it names a defined scope, otherwise fallback.
|
||||
// normalizeScope 在 s 命名了已定义作用域时返回 s,否则返回 fallback。
|
||||
func normalizeScope(s, fallback string) string {
|
||||
if Scope(s).valid() {
|
||||
return s
|
||||
@@ -231,7 +231,7 @@ func normalizeScope(s, fallback string) string {
|
||||
return fallback
|
||||
}
|
||||
|
||||
// normalizeKind returns s when it names a defined kind, otherwise fallback.
|
||||
// normalizeKind 在 s 命名了已定义 kind 时返回 s,否则返回 fallback。
|
||||
func normalizeKind(s, fallback string) string {
|
||||
if Kind(s).valid() {
|
||||
return s
|
||||
|
||||
Reference in new issue
Block a user