mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-15 12:15:16 +00:00
## 背景 仓库早已迁移到 GitHub(`github.com/JianFeeeee/HomeAgent`,CI/CD 都在那边跑), gitcode 保留为**国内只读镜像**(`ci.yml` 的 sync-gitcode-main 快进推送)。 但 go.mod 的 module 路径一直是 `gitcode.com/JianFeeeee/...` —— 既与权威源 不符,`meta.SourceURL`(AGPL §13 的源码提供入口)也指向镜像而非实际运行的代码。 ## 关键区分(这是本次改动安全的前提) 两个 module 是**不同**的,影响面差一个数量级: | module | 谁依赖 | 改它的后果 | |---|---|---| | `.../HomeAgent`(主仓) | **只有主仓自己** | 内部重构,零外部影响 | | `.../homeagent-sdk`(SDK 仓) | 主仓 49 文件 + 所有外部插件 | 需重编插件 | ★ 实测确认:**插件不 import 主仓 module**(`strings qq/plugin.bin | grep HomeAgent` = 0), 只依赖 SDK 仓。而插件与内核的边界是 **RPC 方法名字符串** (`plugin.init` / `tool.invoke`…,见 proc/protocol.go),**与 Go module 名无关**。 ⇒ module 改名**不改变任何 wire 协议**,现网已装的插件照常运行。 SDK 的 GitHub 仓名是 `homeagentsdk`(无连字符),module 路径必须与之精确匹配, 否则 `go get` 会因路径不符失败,故取 `github.com/JianFeeeee/homeagentsdk`。 ## 改动 - `go.mod` / `go.sum`:两个 module 路径 + require/replace - 277 个 Go 文件 + Makefile + 打包脚本的 import 与 ldflags 路径 - SDK 仓 `meta.CoreModule`、`tools/hmapdev` 的 module 声明与工程模板 - 对外文档/链接的仓库地址(README、PLUGIN_DEV、OVERVIEW 等) ## 明确**保留** gitcode 的地方(镜像机制,不能动) - `GITCODE_TOKEN`、`GC_URL="…@gitcode.com"`(CI 同步 job) - `gitcode.com/api/v5/...`(release 附件上传) - git remote `gitcode`(镜像拉取/推送) - `docs/zh/ci-cd-runbook.md`(整篇讲镜像运维) ## 验证 - `go build ./...` / `go vet ./...` 绿 - `go test ./... -count=1` → 48 包 ok / 0 FAIL - hmapdev 工具链编译通过(`third_party/homeagent-sdk/tools/hmapdev`) - 全仓残留检查:module 形态的 `gitcode.com/JianFeeeee` = 0 - 镜像机制抽验:GC_URL / @gitcode / api/v5 / remote 均完好 ## 顺带修正 `hmapdev/templates.go` 的注释此前写「gitcode 的模块不在 proxy.golang.org 上」—— module 已迁 GitHub,该论断不再成立。改为如实说明**为何仍默认 replace 到本机 SDK**: 离线可构建、避免版本漂移、内网可用。理由与托管地无关,故 replace 保留。
173 lines
6.8 KiB
Go
173 lines
6.8 KiB
Go
package core
|
||
|
||
import (
|
||
"github.com/JianFeeeee/HomeAgent/internal/agent/api"
|
||
)
|
||
|
||
// TokenBudget 上下文 token 预算分配结果
|
||
type TokenBudget struct {
|
||
MaxContext int // 模型窗口上限
|
||
TargetUsage int // 目标使用量(受 maxTargetTokens 与 utilizationRate 共同约束)
|
||
FixedTokens int // 固定部分(system prompt base + tools + rules)
|
||
MemoryTokens int // memory context 可用预算
|
||
ContextTokens int // 上下文事件可用预算
|
||
Reserved int // 预留(response 空间)
|
||
}
|
||
|
||
// EstimateTokens / TruncateByTokens 转发到编解码层统一出口。
|
||
//
|
||
// 本包内调用点很多(context.go / process.go / resident.go / tooldefs.go…),
|
||
// 而实现只有一份(api 包,C 化后可选走 C)。保留这两个同名转发,
|
||
// 是为了不把调用点全部改写成 api.EstimateTokens —— 那场改动对行为零收益,
|
||
// 却把「本包依赖 api」这件事铺得到处都是。
|
||
|
||
// EstimateTokens 粗略估算 token 数(转发到 api.EstimateTokens)。
|
||
func EstimateTokens(text string) int { return api.EstimateTokens(text) }
|
||
|
||
// TruncateByTokens 截断字符串至不超过 maxTokens 估计值(转发到 api.TruncateByTokens)。
|
||
func TruncateByTokens(s string, maxTokens int) string { return api.TruncateByTokens(s, maxTokens) }
|
||
|
||
// ContextTuning 是上下文预算的**可调参数**(零值 = 历史默认)。
|
||
//
|
||
// 为何要有它:这些阈值原本全部硬编码在 ComputeTokenBudget 里,
|
||
// 配置面板上一个都没有 —— 用户无法为不同窗口(如 200k)调优,
|
||
// 也无法做「同窗口下不同策略」的对照实验。
|
||
//
|
||
// ★ 两个常量叠加会合成一个**没人看得见的分段函数**:
|
||
//
|
||
// 窗口 ≤ 750000 → 工作面 = 窗口 × 0.8 (80%)
|
||
// 窗口 > 750000 → 工作面 = 600000 (利用率退化成 600000/窗口)
|
||
//
|
||
// 拐点位置由 0.8 与 600000 **共同**决定,改任一个都会挪动它。
|
||
// 现在拐点由 MaxTargetTokens 显式表达,而 MaxTargetTokens < 0 表示不封顶
|
||
// (利用率对所有窗口都真的等于 UtilizationPercent)。
|
||
//
|
||
// 零值语义(升级安全):全部字段为 0 时,结果与硬编码时代**逐值相同**。
|
||
// 详见 context_tuning_test.go 的 TestContextTuningZeroValueEqualsLegacy。
|
||
type ContextTuning struct {
|
||
// UtilizationPercent 是目标窗口利用率(百分比,1-100)。
|
||
// 0 ⇒ 用历史默认 80。
|
||
UtilizationPercent int
|
||
// MaxTargetTokens 是**工作区间**的绝对上限(不是模型窗口)。
|
||
//
|
||
// 0 ⇒ 用历史默认 600000
|
||
// < 0 ⇒ 不封顶(工作面纯由 UtilizationPercent 跟随窗口)
|
||
// > 0 ⇒ 该值作为上限
|
||
//
|
||
// 为何不是模型窗口本身:标称窗口 ≠ 有效窗口。接近满窗口时注意力涣散,
|
||
// 成本与延迟也随 prompt 线性上升。该源实测 990,034 token 仍能返回,
|
||
// 但 600K 才是它的最优工作区间。
|
||
MaxTargetTokens int
|
||
// MemoryRatioPercent 是记忆上下文占「可用预算」的百分比(1-99)。
|
||
//
|
||
// 0 ⇒ 用历史默认:available / 3(整数除法,逐值不变)
|
||
// 1-99 ⇒ available × pct / 100
|
||
//
|
||
// ⚠️ 0 特意不换算成 33%:实测 available/3 与 available×33/100 **不等价**
|
||
//(1000 时是 333 vs 330),换算会让默认值不再等价。
|
||
MemoryRatioPercent int
|
||
// ProtectedCount 是裁剪时**无条件保留**的最近事件条数。
|
||
//
|
||
// 它决定「近处记忆」与「向量检索」的权:条数越大越不容易丢近处信息,
|
||
// 越小越依赖检索准确度。0 ⇒ 用历史默认 10。
|
||
ProtectedCount int
|
||
}
|
||
|
||
// 历史默认值(硬编码时代的常量,改这里等于改所有未配置实例的行为)。
|
||
const (
|
||
defaultUtilizationPercent = 80
|
||
defaultMaxTargetTokens = 600000
|
||
defaultProtectedCount = 10
|
||
// legacyMemoryDivisor 是记忆预算的历史除数(available/3)。
|
||
legacyMemoryDivisor = 3
|
||
)
|
||
|
||
// ComputeTokenBudget 计算各部分的 token 预算(零值 tuning = 历史行为)。
|
||
//
|
||
// 保留这个签名是为了不改既有调用点;需要调参的调用方用 ComputeTokenBudgetTuned。
|
||
func ComputeTokenBudget(provider api.Provider, systemPromptBase string) TokenBudget {
|
||
return ComputeTokenBudgetTuned(provider, systemPromptBase, ContextTuning{})
|
||
}
|
||
|
||
// computeTokenBudget 用本 agent 的配置调参计算上下文预算。
|
||
//
|
||
// 存在理由:ComputeTokenBudget 是包级函数、拿不到 agent 的配置;
|
||
// 若调用点各自传参很容易漏(本仓刚发现 core.agent.max_context_size
|
||
// 被注册进面板却从未被读取 —— 一个不报错的死配置)。
|
||
// 收成方法后,调用点只需 a.computeTokenBudget()。
|
||
func (a *Agent) computeTokenBudget() TokenBudget {
|
||
return ComputeTokenBudgetTuned(a.provider, a.systemPrompt, a.ctxTuning)
|
||
}
|
||
|
||
// ComputeTokenBudgetTuned 按配置计算各部分的 token 预算。
|
||
//
|
||
// 固定部分(system prompt + tools + rules)优先保障,剩余按 MemoryRatioPercent
|
||
// 切给 memory context 与 context events。
|
||
func ComputeTokenBudgetTuned(provider api.Provider, systemPromptBase string, t ContextTuning) TokenBudget {
|
||
// provider 可能为 nil(构造期、或 provider 尚未就绪/已被换掉)。
|
||
// 之前直接调 provider.MaxContextTokens() 会 SIGSEGV —— 实测由contextTopK
|
||
// 在测试里撞出来,但它同样会在「配置早于 provider 就绪」的启动序列上发生。
|
||
var maxCtx int
|
||
if provider != nil {
|
||
maxCtx = provider.MaxContextTokens()
|
||
}
|
||
if maxCtx <= 0 {
|
||
maxCtx = 32768
|
||
}
|
||
|
||
utilPct := t.UtilizationPercent
|
||
if utilPct <= 0 {
|
||
utilPct = defaultUtilizationPercent
|
||
}
|
||
if utilPct > 100 {
|
||
utilPct = 100
|
||
}
|
||
// 整数运算:实测 maxCtx×80/100 与 int(float64(maxCtx)×0.8)
|
||
// 在 42 万个窗口值上逐值相同,且不受浮点表示误差影响。
|
||
targetUsage := maxCtx * utilPct / 100
|
||
|
||
// 上限:0=历史默认(600000),负=不封顶。
|
||
cap := t.MaxTargetTokens
|
||
if cap == 0 {
|
||
cap = defaultMaxTargetTokens
|
||
}
|
||
if cap > 0 && targetUsage > cap {
|
||
targetUsage = cap
|
||
}
|
||
|
||
reserved := maxCtx - targetUsage
|
||
if reserved < 0 {
|
||
reserved = 0
|
||
}
|
||
|
||
fixedTokens := EstimateTokens(systemPromptBase)
|
||
|
||
available := targetUsage - fixedTokens
|
||
if available < 0 {
|
||
available = 0
|
||
}
|
||
|
||
memTokens := available / legacyMemoryDivisor
|
||
if t.MemoryRatioPercent > 0 {
|
||
pct := t.MemoryRatioPercent
|
||
if pct > 99 {
|
||
pct = 99
|
||
}
|
||
memTokens = available * pct / 100
|
||
}
|
||
// 余数归 context:保证 mem + ctx == available(不因取整丢 token)。
|
||
ctxTokens := available - memTokens
|
||
|
||
return TokenBudget{
|
||
MaxContext: maxCtx,
|
||
TargetUsage: targetUsage,
|
||
FixedTokens: fixedTokens,
|
||
MemoryTokens: memTokens,
|
||
ContextTokens: ctxTokens,
|
||
Reserved: reserved,
|
||
}
|
||
}
|
||
|
||
// TruncateByTokens 已移至 api 包(编解码层统一出口,见 codec.go)。
|
||
// 上方已有同名转发,此处不再重复定义。
|