Files
Cyrene_v2/cmd/cyrene/extractor.go
T
AskaEth 52f08dc343
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
docs(cyrene): cmd/cyrene 存量英文注释中文化(22 个非测试文件,仅注释)
- 四组并行完成:main/wire*.go、wire_capacity|channels|runtime、channels|persona_*|emotion|observability、
  voice|plugins|stream|skills|vision|subagent_tool|extractor|mcp|sessionread_tool|terminal_tool。
- 机械校验(git diff -U0 去 // 行与空行)仅剩 wire_frontend.go:84 一处行尾注释:代码前缀
  「\ta.gw = gw 」经 cat -A 与 HEAD 逐字节比对一致(allow 清单已声明)。
- 验收:隔离 worktree(HEAD + 本批 patch)go build/vet 绿、全树 58 包 ok、
  linux/amd64 + darwin/arm64 交叉编译绿;gofmt -l 干净。
- 备注:整树两次红均为负载敏感 flake(backthink / orchestration 的 timeout 类用例单独 -count=20 全过)。
2026-10-04 16:58:06 +08:00

78 lines
2.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"net"
"net/url"
"strings"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/memory"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
)
// memoryOwnerRoleID 是组合根写入提取出的记忆时所用的角色 id;主人是唯一会写入
// 私有记忆的 actor(群聊/陌生人会话根本不提取,design/11 §2)。
const memoryOwnerRoleID = "owner"
// buildMemoryExtractor 选择循环在每轮落定后运行的记忆提取器(design/03 §1
// LLM 异步自动提取):
//
// - provider 栈存在可用的活动组时用 LLM 提取器,由模型去噪并去重真正值得记的内容;
// - 否则用确定性的 ManualExtractor,使无 key 的部署仍能记录主人显式的「记住 X」
// 指令,而不是每轮都让模型调用失败。
//
// 该提取器刻意不持有任何人设:一个进程现在会以多个角色作答(design/22 §4.2),
// 若在此固化某个人设,就会把所有提取出的记忆都归到那一个角色下。循环在持久化时
// 用产生该轮的角色逐条打标(loop.persistMemories)。
//
// 循环会把 LLM 提取器分离到后台 goroutine(ExtractAsync);见
// internal/runtime/loop/memory.go。
func (a *app) buildMemoryExtractor() memory.Extractor {
if llmGroupUsable(a.llm) {
a.log.Info("memory extractor: llm", "model", a.llm.Model(), "provider", a.llm.Provider())
return memory.NewLLMExtractor(a.llm, "", memoryOwnerRoleID)
}
a.log.Warn("memory extractor: manual (no usable llm model group)")
return memory.NewManualExtractor("", memoryOwnerRoleID)
}
// llmGroupUsable 报告运行时栈的活动模型组是否真的可调用:模型与 base URL 是必需
// 项,且要么配置了 API key,要么端点是本地/内网地址 —— 后者无 key 调用属正常情况
// (自托管的 OpenAI 兼容服务)。
//
// 它刻意只做廉价的静态检查,绝不发探测请求:启动不该花掉一次模型调用。看起来可用
// 但调用时才失败的组,只会记一条提取错误日志(提取失败从不致命)。
func llmGroupUsable(llm *model.RuntimeLLM) bool {
if llm == nil {
return false
}
group := llm.Group()
if strings.TrimSpace(group.Model) == "" || strings.TrimSpace(group.BaseURL) == "" {
return false
}
if strings.TrimSpace(group.APIKey) != "" {
return true
}
return keylessEndpoint(group.BaseURL)
}
// keylessEndpoint 报告 baseURL 是否指向回环或内网主机 —— 这类地址上缺少 API key
// 是预期情况,而非配置错误。
func keylessEndpoint(baseURL string) bool {
parsed, err := url.Parse(strings.TrimSpace(baseURL))
if err != nil {
return false
}
host := parsed.Hostname()
if host == "" {
return false
}
if strings.EqualFold(host, "localhost") {
return true
}
ip := net.ParseIP(host)
if ip == nil {
return false
}
return ip.IsLoopback() || ip.IsPrivate()
}