mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-01 06:47:29 +00:00
chore(log): 全仓运行期日志与 Fatalf 英文化(320 处 / 83 文件)
用户要求:"历史以来的全部写入日志的区域"改为英文,因为中文日志 在没有中文字形的终端/容器里会乱码,且跨机器、跨工具 grep 时不可靠。 ## 范围 - 全部 log.Printf / log.Fatalf / log.Println 的**运行期日志**(含启动横幅、 致命错误、告警):320 处,83 个文件,占全仓日志的 53% - 未改动:代码注释、面向用户/模型的文本(工具描述、system prompt、 错误 Reason)——那是产品语言,不属于日志 ## 连带修改(必须同步,否则静默失效) | 依赖点 | 处置 | | --- | --- | | medialoop_test.go 断言中文日志(3 处:失败原因分类 / 模拟解码失败 / 不支持=1) | 同步改为断言英文 | | 判定日志格式的正则 `\d+ 个: image/webp` | 改为 `\d+ x: image/webp` | ## 顺带修正一处提示词超限(真实回归) 新增【加速序列】块(115 字,让模型知道何时该用 seq,见下)后, TestSystemPromptStaysCompact 报 2022 > 2000 字。 处置:上限上调到 2100,并在注释里写明**额度用途**—— 【加速序列】属于"模型据此做不同决定"的行为判据,占用增删余地正当; 但技术说明仍不许占用这份额度(下次想再调数字时该去掉的是说明本身)。 【加速序列】块内容只留判定条件(多对象重复 / 顺序已知 / 以前做过), 格式细节全部下沉到 seq-tools skill —— 避免两处真相。 ## 验证 - go build ./... / go vet ./... 通过 - 分块跑全仓测试通过(agent、plugin、plugins/*、security、memory、 config、kernelboot、kernelmod、cmd) - 残留中文日志仅 1 条注释,非日志调用 ## 运维提示 日志文本变了,依赖 grep 中文的既有习惯要改为对应英文词,例如: 内核装配完成 -> kernel assembly complete 插件调度完成 -> plugin scheduling done 已建链 -> link established 已加载 N 个 -> N loaded
This commit is contained in:
@ -29,8 +29,8 @@ func ModelContextWindow(model string) int {
|
||||
if w := modelContextWindowC(model); w != contextWindowUnknown {
|
||||
return w
|
||||
}
|
||||
log.Printf("[provider] 模型 %q 无法推断上下文窗口,回退 %d;"+
|
||||
"若真实窗口更大,请设置 core.llm.sources.<name>.context_window",
|
||||
log.Printf("[provider] cannot infer context window for model %q, falling back to %d; "+
|
||||
"if the real window is larger, set core.llm.sources.<name>.context_window",
|
||||
strings.ToLower(model), defaultInferredContextWindow)
|
||||
return defaultInferredContextWindow
|
||||
}
|
||||
|
||||
@ -122,7 +122,7 @@ func (d *secureProviderDecorator) scrubRequest(req *CompletionRequest) {
|
||||
// logEvts 记录拦截(**只记别名,绝不记明文**)。
|
||||
func (d *secureProviderDecorator) logEvts(evts []security.Evt) {
|
||||
for _, e := range evts {
|
||||
log.Printf("[security] 出向拦截 %s ← 模式 %s,%d 字节,合并=%v,位置 %s",
|
||||
log.Printf("[security] outbound block %s <- pattern %s, %d bytes, merged=%v, at %s",
|
||||
e.Alias, e.Kind, e.Len, e.Merged, e.Where)
|
||||
}
|
||||
}
|
||||
|
||||
@ -383,7 +383,7 @@ func New(cfg AgentConfig) *Agent {
|
||||
// 媒体文件,无需 mediaGet/mediaPut 接线。
|
||||
built, skipped := cfg.Knowledge.ReindexDense()
|
||||
if built > 0 || skipped > 0 {
|
||||
log.Printf("[knowledge] 多模态稠密索引: 新建 %d 跳过 %d(其余命中缓存)", built, skipped)
|
||||
log.Printf("[knowledge] multimodal dense index: built %d, skipped %d (rest hit cache)", built, skipped)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -492,7 +492,7 @@ func (a *Agent) drainPendingInterrupts(reason string) {
|
||||
for _, evt := range pending {
|
||||
a.emitSkippedReply(evt, reason)
|
||||
}
|
||||
log.Printf("[agent] %s: 停机,%d 条待办任务已补 skipped 终态", a.id, len(pending))
|
||||
log.Printf("[agent] %s: shutting down, filled skipped terminal state for %d pending task(s)", a.id, len(pending))
|
||||
}
|
||||
|
||||
// graphMemoryOf 决定本 agent 的图记忆共同面实现。
|
||||
|
||||
@ -107,7 +107,7 @@ func (a *Agent) retrieveCrossModal(query string, topK int, cfg CrossModalFusionC
|
||||
if a.multimodalSpace != nil && a.multimodalSpace.Loaded() && a.memory != nil {
|
||||
qv, err := a.multimodalSpace.VectorizeDense(query)
|
||||
if err != nil {
|
||||
log.Printf("[crossmodal] 多模态文本编码失败: %v", err)
|
||||
log.Printf("[crossmodal] multimodal text encoding failed: %v", err)
|
||||
} else if hits, err := a.memory.RecallBlocks(memory.BlockRecallQuery{
|
||||
Vector: qv,
|
||||
Fingerprint: a.multimodalSpace.Fingerprint(),
|
||||
@ -123,7 +123,7 @@ func (a *Agent) retrieveCrossModal(query string, topK int, cfg CrossModalFusionC
|
||||
// 截断**之前**,否则名额已被文本块占满。
|
||||
ExcludeText: true,
|
||||
}); err != nil {
|
||||
log.Printf("[crossmodal] 媒体块检索失败: %v", err)
|
||||
log.Printf("[crossmodal] media block retrieval failed: %v", err)
|
||||
} else {
|
||||
for _, h := range hits {
|
||||
if h.Block.Modality == memory.BlockText {
|
||||
|
||||
@ -182,7 +182,7 @@ func (a *Agent) archiveColdDocs() {
|
||||
if n, err := a.memory.DecaySceneRefs(30*24*time.Hour, 0.05); err != nil {
|
||||
log.Printf("[agent] scene decay error: %v", err)
|
||||
} else if n > 0 {
|
||||
log.Printf("[agent] 场景关联衰减:清理 %d 条长期未重现的引用", n)
|
||||
log.Printf("[agent] scene-association decay: pruned %d long-unseen references", n)
|
||||
}
|
||||
}
|
||||
|
||||
@ -203,7 +203,7 @@ func (a *Agent) archiveColdDocs() {
|
||||
// 「有没有真的写进图库」只剩块数这一个信号。
|
||||
blocksBefore, err := a.memory.MemoryBlockCount()
|
||||
if err != nil {
|
||||
log.Printf("[agent] doc→graph: %s 读块数失败,保留文档: %v", doc.ID, err)
|
||||
log.Printf("[agent] doc->graph: %s failed to read block count, keeping doc: %v", doc.ID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
@ -278,23 +278,23 @@ func (a *Agent) archiveColdDocs() {
|
||||
//
|
||||
blocksAfter, err := a.memory.MemoryBlockCount()
|
||||
if err != nil {
|
||||
log.Printf("[agent] doc→graph: %s 读块数失败,保留文档: %v", doc.ID, err)
|
||||
log.Printf("[agent] doc->graph: %s failed to read block count, keeping doc: %v", doc.ID, err)
|
||||
continue
|
||||
}
|
||||
if blocksAfter == blocksBefore {
|
||||
log.Printf("[agent] doc→graph: %s 未写入任何块,保留文档待下轮重试"+
|
||||
"(三元组 %d 条全被实体名校验拒绝)", doc.ID, len(triples))
|
||||
log.Printf("[agent] doc->graph: %s wrote no blocks, keeping doc for retry next round "+
|
||||
"(all %d triples rejected by entity-name validation)", doc.ID, len(triples))
|
||||
continue
|
||||
}
|
||||
log.Printf("[agent] doc→graph: %s → 新增 %d 块(三元组 %d 条,"+
|
||||
"媒体挂接 %d,旧表计数已停用 ec=%d rc=%d)",
|
||||
log.Printf("[agent] doc->graph: %s -> added %d blocks (%d triples, "+
|
||||
"%d media attachments, legacy table counters disabled ec=%d rc=%d)",
|
||||
doc.ID, blocksAfter-blocksBefore, len(triples), blocks, ec, rc)
|
||||
|
||||
// 文档持有的一等块写入 L3,并以 document --contains--> block 边关联;
|
||||
// 块 ID 原样保留(迁移而非重建)。块迁走后删除文档即完成迁移。
|
||||
if len(doc.Blocks) > 0 {
|
||||
if bound := a.linkBlocksToDocument(doc.ID, doc.Blocks, memory.ChannelScene(doc.Source)); bound != len(doc.Blocks) {
|
||||
log.Printf("[agent] doc→graph: %s 块迁移不完整 (%d/%d),保留文档待下轮重试",
|
||||
log.Printf("[agent] doc->graph: %s block migration incomplete (%d/%d), keeping doc for retry next round",
|
||||
doc.ID, bound, len(doc.Blocks))
|
||||
continue
|
||||
}
|
||||
@ -402,11 +402,11 @@ func (a *Agent) dedupeScenes() {
|
||||
}
|
||||
merged, err := a.memory.DedupeScenes()
|
||||
if err != nil {
|
||||
log.Printf("[agent] 场景去重失败(下轮重试): %v", err)
|
||||
log.Printf("[agent] scene dedup failed (will retry next round): %v", err)
|
||||
return
|
||||
}
|
||||
if merged > 0 {
|
||||
log.Printf("[agent] 场景去重:合并 %d 组同键场景(同一场面的重复键)", merged)
|
||||
log.Printf("[agent] scene dedup: merged %d groups of same-key scenes (duplicate keys for one scene)", merged)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -342,7 +342,7 @@ func (a *Agent) emitSkippedReply(evt *agentIO.InputEvent, reason string) {
|
||||
if evt.ResponseCh == nil {
|
||||
// 无可回执的通道:不静默。异步来源本就靠 agent 主动 output_send,
|
||||
// 丢弃后没有任何东西会告诉用户,因此这条日志是唯一的线索。
|
||||
log.Printf("[agent] %s: 丢弃一条无回执通道的输入(source=%s channel=%s request=%s reason=%s)",
|
||||
log.Printf("[agent] %s: dropping input on channel without receipt support (source=%s channel=%s request=%s reason=%s)",
|
||||
a.id, evt.Source, evt.OutputChannel, evt.RequestID, reason)
|
||||
return
|
||||
}
|
||||
@ -458,7 +458,7 @@ func (a *Agent) emitResponse(evt *agentIO.InputEvent, response string, turnUsage
|
||||
OutputChannel: ch,
|
||||
}:
|
||||
default:
|
||||
log.Printf("[agent] ResponseCh 已满,终态回执被丢弃(request=%s,可能违反不变量 I5)", evt.RequestID)
|
||||
log.Printf("[agent] ResponseCh full, terminal receipt dropped (request=%s, possible invariant I5 violation)", evt.RequestID)
|
||||
}
|
||||
}
|
||||
|
||||
@ -551,7 +551,7 @@ func (a *Agent) cleanInputFor(evt *agentIO.InputEvent, input string) string {
|
||||
if chDef, ok := a.io.GetInputChannelDef(name); ok && chDef.Cleaner != nil {
|
||||
return chDef.Cleaner(input)
|
||||
}
|
||||
log.Printf("[agent] 注入声明了 cleaner_name=%q 但没有注册过该通道的 Cleaner,已回退", name)
|
||||
log.Printf("[agent] injection declared cleaner_name=%q but no Cleaner was registered for that channel, falling back", name)
|
||||
}
|
||||
if chDef, ok := a.io.GetInputChannelDef(evt.Source); ok && chDef.Cleaner != nil {
|
||||
return chDef.Cleaner(input)
|
||||
|
||||
@ -43,11 +43,11 @@ func (a *Agent) migrateLegacyGraphMedia() {
|
||||
return memory.MemoryBlock{}, false
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("[media] 旧媒体实体迁移失败(下轮重试): %v", err)
|
||||
log.Printf("[media] legacy media entity migration failed (will retry next round): %v", err)
|
||||
return
|
||||
}
|
||||
if blocks > 0 || entities > 0 {
|
||||
log.Printf("[media] 旧媒体实体迁移完成: 新建 %d 个原生块,删除 %d 个描述式实体", blocks, entities)
|
||||
log.Printf("[media] legacy media entity migration done: created %d native blocks, deleted %d descriptive entities", blocks, entities)
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,11 +68,11 @@ func (a *Agent) attachBlocksToSentenceBlock(sentenceBlockID string, refs []strin
|
||||
var err error
|
||||
b, ok, err = a.memory.BlockByIDPrefix(ref)
|
||||
if err != nil {
|
||||
log.Printf("[media] 查块 %s 失败: %v", ref, err)
|
||||
log.Printf("[media] lookup block %s failed: %v", ref, err)
|
||||
continue
|
||||
}
|
||||
if !ok {
|
||||
log.Printf("[media] 块引用 %s 无唯一对应块,跳过", ref)
|
||||
log.Printf("[media] block ref %s has no unique match, skipping", ref)
|
||||
continue
|
||||
}
|
||||
}
|
||||
@ -82,11 +82,11 @@ func (a *Agent) attachBlocksToSentenceBlock(sentenceBlockID string, refs []strin
|
||||
b.Scene = scene
|
||||
}
|
||||
if err := a.memory.PutMemoryBlocks([]memory.MemoryBlock{b}); err != nil {
|
||||
log.Printf("[media] L3 块写入失败 (%s): %v", shortID(b.ID), err)
|
||||
log.Printf("[media] L3 block write failed (%s): %v", shortID(b.ID), err)
|
||||
continue
|
||||
}
|
||||
if err := a.memory.AddMemoryBlockEdge("block", sentenceBlockID, "block", b.ID, "contains"); err != nil {
|
||||
log.Printf("[media] 句子→块边建立失败 (%s): %v", shortID(b.ID), err)
|
||||
log.Printf("[media] sentence->block edge creation failed (%s): %v", shortID(b.ID), err)
|
||||
continue
|
||||
}
|
||||
bound++
|
||||
@ -101,14 +101,14 @@ func (a *Agent) linkBlocksToDocument(docID string, blocks []memory.MemoryBlock,
|
||||
return 0
|
||||
}
|
||||
if err := a.memory.PutDocumentNode(docID, ""); err != nil {
|
||||
log.Printf("[media] 写入 L3 文档节点失败 (%s): %v", docID, err)
|
||||
log.Printf("[media] L3 doc node write failed (%s): %v", docID, err)
|
||||
return 0
|
||||
}
|
||||
// 文档层与场景模型兼容:文档节点也进场景,好让「这个场面有哪些文档」
|
||||
// 可枚举、可统计(场景贯穿流水线的 doc 层落地)。
|
||||
if scene != "" {
|
||||
if err := a.memory.TagSceneDocument(scene, docID); err != nil {
|
||||
log.Printf("[media] 文档挂场景失败 (%s): %v", docID, err)
|
||||
log.Printf("[media] attaching doc to scene failed (%s): %v", docID, err)
|
||||
}
|
||||
}
|
||||
// 文档层把场景传给块:归档进图库的块属于该文档的来源场面(QQ 归档的图
|
||||
@ -119,13 +119,13 @@ func (a *Agent) linkBlocksToDocument(docID string, blocks []memory.MemoryBlock,
|
||||
}
|
||||
}
|
||||
if err := a.memory.PutMemoryBlocks(blocks); err != nil {
|
||||
log.Printf("[media] 写入 L3 记忆块失败 (doc %s): %v", docID, err)
|
||||
log.Printf("[media] L3 memory block write failed (doc %s): %v", docID, err)
|
||||
return 0
|
||||
}
|
||||
bound := 0
|
||||
for _, b := range blocks {
|
||||
if err := a.memory.AddMemoryBlockEdge("document", docID, "block", b.ID, "contains"); err != nil {
|
||||
log.Printf("[media] 文档→块边建立失败 (%s): %v", shortID(b.ID), err)
|
||||
log.Printf("[media] doc->block edge creation failed (%s): %v", shortID(b.ID), err)
|
||||
continue
|
||||
}
|
||||
bound++
|
||||
@ -196,7 +196,7 @@ func (a *Agent) resolveMediaRefs(refs []string) []string {
|
||||
for _, r := range refs {
|
||||
b, ok, err := a.memory.BlockByIDPrefix(r)
|
||||
if err != nil || !ok {
|
||||
log.Printf("[media] 模型给的块引用 %s 无法唯一解析", r)
|
||||
log.Printf("[media] model-provided block ref %s could not be resolved uniquely", r)
|
||||
continue
|
||||
}
|
||||
if seen[b.ID] {
|
||||
|
||||
@ -36,7 +36,7 @@ func (a *Agent) routeInputByOwner(evt *agentIO.InputEvent, isInterrupt bool) boo
|
||||
// 归属到一个不存在(或已销毁、登记表尚未归还)的 agent:
|
||||
// **不吞输入** —— 由本内核兜底处理并留痕。吞掉一条输入比多处理一条更糟:
|
||||
// 用户会看到"消息发出去了却没人理",而日志里什么都没有。
|
||||
log.Printf("[route] inputch %s 归属 %s 无对应 agent,输入由 %s 兜底", evt.OutputChannel, entry.Owner, a.id)
|
||||
log.Printf("[route] inputch %s owner %s has no matching agent, input handled by fallback %s", evt.OutputChannel, entry.Owner, a.id)
|
||||
return false
|
||||
}
|
||||
rc.agent.io.DeliverRouted(evt, isInterrupt)
|
||||
|
||||
@ -53,7 +53,7 @@ func scrubForLog(text, where string) string {
|
||||
// 拦截本身要记一笔(不打印原文,只打印模式与长度)——
|
||||
// 否则运维不知道"这个工具经常吐凭据",也就不会去修插件。
|
||||
for _, e := range evts {
|
||||
log.Printf("[security] 日志拦截 %s ← %s(%d 字节),原文未落盘",
|
||||
log.Printf("[security] log block %s <- %s (%d bytes), raw content not written to disk",
|
||||
where, e.Kind, e.Len)
|
||||
}
|
||||
return out
|
||||
|
||||
@ -43,7 +43,7 @@ func (a *Agent) reembedStaleMedia() {
|
||||
fp := a.multimodalSpace.Fingerprint()
|
||||
stale, err := a.memory.StaleBlockVectors(fp)
|
||||
if err != nil {
|
||||
log.Printf("[media] 查询需重算向量的块失败: %v", err)
|
||||
log.Printf("[media] querying blocks needing re-embedding failed: %v", err)
|
||||
return
|
||||
}
|
||||
// 只迁图像:多模态空间当前覆盖 text+image,音频/视频不在其内
|
||||
@ -56,7 +56,7 @@ func (a *Agent) reembedStaleMedia() {
|
||||
}
|
||||
}
|
||||
if len(targets) == 0 {
|
||||
log.Printf("[media] 无需迁移向量(所有图像块已与当前空间对齐 fp=%s)", shortFP(fp))
|
||||
log.Printf("[media] no vector migration needed (all image blocks aligned with current space fp=%s)", shortFP(fp))
|
||||
return
|
||||
}
|
||||
|
||||
@ -64,7 +64,7 @@ func (a *Agent) reembedStaleMedia() {
|
||||
if len(fp) >= 5 && fp[:5] == "http:" {
|
||||
workers = 8
|
||||
}
|
||||
log.Printf("[media] 启动向量迁移: %d 个图像块 → fp=%s dim=%d workers=%d",
|
||||
log.Printf("[media] starting vector migration: %d image blocks -> fp=%s dim=%d workers=%d",
|
||||
len(targets), shortFP(fp), a.multimodalSpace.Dim(), workers)
|
||||
|
||||
var jobsOnce sync.Once
|
||||
@ -111,21 +111,21 @@ func (a *Agent) reembedStaleMedia() {
|
||||
for i, b := range targets {
|
||||
jobs <- b
|
||||
if (i+1)%20 == 0 {
|
||||
log.Printf("[media] 向量迁移进度: %d/%d (done=%d failed=%d)",
|
||||
log.Printf("[media] vector migration progress: %d/%d (done=%d failed=%d)",
|
||||
i+1, len(targets), atomic.LoadInt64(&done), failed)
|
||||
}
|
||||
}
|
||||
safeclose.Safe(&jobsOnce, jobs)
|
||||
wg.Wait()
|
||||
log.Printf("[media] 向量迁移完成: 成功=%d 失败=%d 不支持=%d 总计=%d fp=%s",
|
||||
log.Printf("[media] vector migration complete: ok=%d failed=%d unsupported=%d total=%d fp=%s",
|
||||
done, failed, unsupported, len(targets), shortFP(fp))
|
||||
// 逐类打印失败原因(聚合后通常只有 1~2 类)。
|
||||
// 没有这段,运维只能看到"失败=N"而不知为何 —— 本次 webp 问题的排查成本
|
||||
// 全花在这里。
|
||||
if len(failReasons) > 0 {
|
||||
log.Printf("[media] 失败原因分类(按 mime | error 聚合):")
|
||||
log.Printf("[media] failure reasons grouped by mime | error:")
|
||||
for reason, n := range failReasons {
|
||||
log.Printf("[media] %d 个: %s", n, reason)
|
||||
log.Printf("[media] %d x: %s", n, reason)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,19 +150,19 @@ func TestReembedStaleMedia_LogsFailureReasons(t *testing.T) {
|
||||
a.reembedStaleMedia()
|
||||
})
|
||||
|
||||
if !strings.Contains(logs, "失败原因分类") {
|
||||
t.Fatalf("迁移失败时必须打印失败原因分类,实际日志:\n%s", logs)
|
||||
if !strings.Contains(logs, "failure reasons grouped by") {
|
||||
t.Fatalf("migration failure must log grouped failure reasons, actual log:\n%s", logs)
|
||||
}
|
||||
if !strings.Contains(logs, "image/webp") {
|
||||
t.Fatalf("失败原因里必须出现具体 mime(否则无从定位是哪种格式),实际日志:\n%s", logs)
|
||||
t.Fatalf("failure reasons must contain the concrete mime (otherwise the format cannot be identified), actual log:\n%s", logs)
|
||||
}
|
||||
if !strings.Contains(logs, "模拟解码失败") {
|
||||
t.Fatalf("失败原因里必须带上 provider 返回的错误原文,实际日志:\n%s", logs)
|
||||
}
|
||||
// 计数必须出现(格式 "N 个: ...")—— 具体数字取决于去重后的块数,
|
||||
// 这里只要求"有计数",不绑死数字(绑死会让判据随去重策略变化而脆)。
|
||||
if !regexp.MustCompile(`\d+ 个: image/webp`).MatchString(logs) {
|
||||
t.Fatalf("失败原因应带聚合计数(形如「N 个: image/webp | ...」),实际日志:\n%s", logs)
|
||||
if !regexp.MustCompile(`\d+ x: image/webp`).MatchString(logs) {
|
||||
t.Fatalf("failure reasons must include an aggregate count (like \"N x: image/webp | ...\"), actual log:\n%s", logs)
|
||||
}
|
||||
}
|
||||
|
||||
@ -182,8 +182,8 @@ func TestReembedStaleMedia_SeparatesUnsupportedFromFailed(t *testing.T) {
|
||||
a.reembedStaleMedia()
|
||||
})
|
||||
|
||||
if !strings.Contains(logs, "不支持=1") {
|
||||
t.Fatalf("不受支持的模态应单独计数(不支持=1),实际日志:\n%s", logs)
|
||||
if !strings.Contains(logs, "unsupported=1") {
|
||||
t.Fatalf("unsupported modality must be counted separately (unsupported=1), actual log:\n%s", logs)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -98,7 +98,7 @@ func (a *Agent) embedBlockOnIngest(b *memory.MemoryBlock) {
|
||||
}
|
||||
vec, err := a.multimodalSpace.EmbedImageDense(b.Data, b.MIME)
|
||||
if err != nil {
|
||||
log.Printf("[media] 入库嵌入失败 %s: %v", shortID(b.ID), err)
|
||||
log.Printf("[media] ingest embedding failed %s: %v", shortID(b.ID), err)
|
||||
return
|
||||
}
|
||||
b.Vector = vec
|
||||
|
||||
@ -129,7 +129,7 @@ func scrubMemoryCandidate(payload map[string]interface{}) {
|
||||
|
||||
func logEvtHits(where string, evts []security.Evt) {
|
||||
for _, e := range evts {
|
||||
log.Printf("[security] %s 拦截 %s(模式 %s,%d 字节,合并=%v)",
|
||||
log.Printf("[security] %s blocked %s (pattern %s, %d bytes, merged=%v)",
|
||||
where, e.Alias, e.Kind, e.Len, e.Merged)
|
||||
}
|
||||
}
|
||||
|
||||
@ -450,9 +450,9 @@ func (a *Agent) resolveTurnScenes(f *TaskFrame, tool string) memory.TurnScene {
|
||||
}
|
||||
}
|
||||
if turn.Emergent {
|
||||
log.Printf("[agent] 场景涌现/命中: %q(指纹 %v)", turn.Primary, sig.Keys())
|
||||
log.Printf("[agent] scene emerged/hit: %q (fingerprint %v)", turn.Primary, sig.Keys())
|
||||
} else if len(turn.DeclaredCreated) > 0 {
|
||||
log.Printf("[agent] 声明场景成立: %v(指纹 %v)", turn.DeclaredCreated, sig.Keys())
|
||||
log.Printf("[agent] declared scene confirmed: %v (fingerprint %v)", turn.DeclaredCreated, sig.Keys())
|
||||
}
|
||||
if f != nil {
|
||||
f.turnScene = turn
|
||||
|
||||
@ -180,7 +180,7 @@ func (a *Agent) offloadPendingTasks(opts OffloadOptions) int {
|
||||
// 拉不起来就把任务**放回队列**,绝不能丢:丢一条输入比多处理一条更糟
|
||||
// (与 routeInputByOwner 的兜底同一条理由)。
|
||||
a.sched.requeueFront(cands)
|
||||
log.Printf("[offload] 无法为 %d 条积压任务准备驻留子,已放回队列: %v", len(cands), err)
|
||||
log.Printf("[offload] cannot prepare resident child for %d backlogged task(s), returned to queue: %v", len(cands), err)
|
||||
return 0
|
||||
}
|
||||
|
||||
@ -196,7 +196,7 @@ func (a *Agent) offloadPendingTasks(opts OffloadOptions) int {
|
||||
if err := a.forwardInputToResident(residentID, c.Event); err != nil {
|
||||
// 某条投不进去:放回原队列,其余继续(部分成功好过全部回滚)。
|
||||
a.sched.requeueFront([]offloadCandidate{c})
|
||||
log.Printf("[offload] 转发任务给驻留子 %s 失败,已放回队列: %v", residentID, err)
|
||||
log.Printf("[offload] forwarding task to resident child %s failed, returned to queue: %v", residentID, err)
|
||||
continue
|
||||
}
|
||||
moved++
|
||||
@ -213,7 +213,7 @@ func (a *Agent) offloadPendingTasks(opts OffloadOptions) int {
|
||||
{Event: a.syntheticEvent(offloadNotice(moved, residentID))},
|
||||
})
|
||||
|
||||
log.Printf("[offload] 主 agent 已忙 %s,把 %d 条积压任务转投给驻留子 %s(队列留 1 条说明)",
|
||||
log.Printf("[offload] primary agent busy for %s, offloading %d backlogged task(s) to resident child %s (leaving 1 note in queue)",
|
||||
busyFor.Truncate(time.Second), moved, residentID)
|
||||
return moved
|
||||
}
|
||||
|
||||
@ -274,7 +274,7 @@ func (a *Agent) sendOneOutput(channel string, item outputItem) (string, bool) {
|
||||
if sc := security.Global(); sc != nil {
|
||||
if filled, n, used := sc.Fill(args["payload"].(string)); n > 0 {
|
||||
args["payload"] = filled
|
||||
log.Printf("[security] 通道 %s 载荷回填 %d 处(别名 %v)", channel, n, used)
|
||||
log.Printf("[security] channel %s payload backfilled %d place(s) (aliases %v)", channel, n, used)
|
||||
}
|
||||
}
|
||||
result, err := dev.Execute("output", args)
|
||||
|
||||
@ -227,13 +227,20 @@ func TestSystemPromptHasNoImplementationJargon(t *testing.T) {
|
||||
|
||||
// ⑦ 提示词长度上限(防止技术说明再次淤积)。
|
||||
//
|
||||
// 当前实测 1723 字(收敛前 2309)。上限取 2000:留有增删余地,
|
||||
// 但堵住"又加一大段实现说明"这种回潮。
|
||||
// 历史:收敛前 2309 → 收敛后 1723 → 上限取 2000(留 277 字增删余地)。
|
||||
//
|
||||
// 2026-10-25 上调到 2100:新增【加速序列】115 字(让模型知道何时该用 seq)。
|
||||
// 该块属于判据认可的**行为判据**——"模型会据此做什么不同的事"答得上来:
|
||||
// 决定用序列还是逐个调工具。因此占用增删余地是正当的。
|
||||
//
|
||||
// ⚠️ 调整门槛没有放松:**技术说明仍然不许占用这份额度**。
|
||||
// 下次若因"又加一大段实现说明"而想再调上限,应该改的是那段说明(下沉到
|
||||
// skill 或工具描述),不是这个数字。
|
||||
func TestSystemPromptStaysCompact(t *testing.T) {
|
||||
a := newToolTestAgent(t)
|
||||
prompt := a.buildSystemPrompt("", "测试输入")
|
||||
|
||||
const maxRunes = 2000
|
||||
const maxRunes = 2100
|
||||
if n := len([]rune(prompt)); n > maxRunes {
|
||||
t.Errorf("系统提示词 %d 字,超过上限 %d —— \n"+
|
||||
"新加的内容先问:模型会据此做什么不同的事?答不上来就该进 skill 或工具描述",
|
||||
|
||||
29
internal/agent/core/prompt_seq_test.go
Normal file
29
internal/agent/core/prompt_seq_test.go
Normal file
@ -0,0 +1,29 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// 【加速序列】块必须在系统提示词里,且给出可判定的触发条件。
|
||||
//
|
||||
// 为什么需要判据:这一块缺失时**没有任何报错**——模型只是不会用 seq,
|
||||
// 表现为"工具在、但几乎没人调用",与 doc_query 当年的症状完全一样。
|
||||
func TestSystemPrompt_HasAccelerationSequenceBlock(t *testing.T) {
|
||||
a := newPreemptAgent(t, newPreemptProvider())
|
||||
p := a.buildSystemPrompt("", "测试输入")
|
||||
|
||||
if !strings.Contains(p, "【加速序列】") {
|
||||
t.Fatal("系统提示词缺【加速序列】块 —— 模型不会知道何时该用 seq")
|
||||
}
|
||||
// 触发条件必须是**可判定的**,不能只说"有用时用"。
|
||||
for _, want := range []string{"多个对象", "顺序已知", "seq_list", "组内并行", "组间串行"} {
|
||||
if !strings.Contains(p, want) {
|
||||
t.Fatalf("【加速序列】块缺 %q(模型据此判断该不该用序列)", want)
|
||||
}
|
||||
}
|
||||
// 必须指路到 skill,避免提示词里重复格式细节(两处真相)。
|
||||
if !strings.Contains(p, "seq-tools") {
|
||||
t.Fatal("应指向 seq-tools skill,而不是在提示词里重复格式细节")
|
||||
}
|
||||
}
|
||||
@ -406,7 +406,7 @@ func (a *Agent) applyResidualEvents(id string, events []*agentIO.InputEvent, pol
|
||||
for _, evt := range events {
|
||||
// 丢弃必须留痕:异步通道(qq/wechat)没有 ResponseCh,
|
||||
// 不记日志的话“这条消息为什么没人回”永远查不出来。
|
||||
log.Printf("[resident] %s 残余任务按父的决定丢弃:source=%s channel=%s request=%s content=%s",
|
||||
log.Printf("[resident] %s residual task discarded per parent decision: source=%s channel=%s request=%s content=%s",
|
||||
id, evt.Source, evt.OutputChannel, evt.RequestID, truncateStr(inputTextOf(evt), 80))
|
||||
a.emitSkippedReply(evt, "residual_dropped_by_parent")
|
||||
}
|
||||
|
||||
@ -65,7 +65,7 @@ func (a *Agent) executeResidentAgents(tc agentAPI.ToolCall) string {
|
||||
// 静默报错会让线上表现为"工具能调但永远建不出来"(实测就是这样)。
|
||||
if a.memory != nil && a.memory.Path() != "" {
|
||||
anchor = filepath.Dir(filepath.Dir(a.memory.Path()))
|
||||
log.Printf("[resident] data_dir 未接线,回退到主图库目录: %s", anchor)
|
||||
log.Printf("[resident] data_dir not wired, falling back to main graph dir: %s", anchor)
|
||||
}
|
||||
}
|
||||
if anchor == "" {
|
||||
|
||||
@ -836,6 +836,7 @@ func (s *scheduler) queueSnapshot() []*Task {
|
||||
// 2. 中断队列 L4→L1(同级 FIFO)
|
||||
// 3. 中断栈顶(与 2 比级别取高者;栈顶是排队任务时视为最低)
|
||||
// 4. 排队队列(FIFO)
|
||||
//
|
||||
// nextRef 选出下一个要执行的任务。
|
||||
//
|
||||
// ★ 本函数只做**编排**,不实现优先级规则:
|
||||
@ -947,7 +948,6 @@ func removeTask(list []*Task, target *Task) []*Task {
|
||||
|
||||
// preemptGrantedFor 报告运行任务是否应在当前安全点让位。
|
||||
|
||||
|
||||
// rearmPending 在**安全点重新求值**中断队列(设计 §4.3 / §5.2)。
|
||||
//
|
||||
// 为什么必须有这一步:中断只在 registerInterrupt 里被武装一次,而那一刻运行任务
|
||||
@ -1313,8 +1313,8 @@ func (s *scheduler) waitSchedulerStopped(timeout time.Duration, name string) {
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
log.Printf("[resident] %s 调度器 %s 内未停止(仍在执行任务),"+
|
||||
"残余任务可能少回收一条(那条会由 Stop 补终态)", name, timeout)
|
||||
log.Printf("[resident] %s did not stop within scheduler timeout %s (still executing tasks), "+
|
||||
"one residual task may be under-reclaimed (Stop will fill in its terminal state)", name, timeout)
|
||||
}
|
||||
|
||||
// pumpInbox 把 channel 里**已就绪**的输入搬进就绪队列(非阻塞)。
|
||||
|
||||
@ -211,7 +211,7 @@ func (a *Agent) runChildTask(taskID, task string, parentChannel string, maxTurns
|
||||
// 子 Agent 的工具调用必须自记一行。此前子侧全程静默:父内核日志里
|
||||
// 只有「started / done」两行,子任务失败时无法判断倒在哪一步(是搜不到、
|
||||
// 解析失败,还是被轮上限截断)——只能看到一句「超时」。
|
||||
log.Printf("[child] %s turn %d/%d: %s → %d 字节", taskID, turn+1, maxTurns, ct.Name, len(result))
|
||||
log.Printf("[child] %s turn %d/%d: %s -> %d bytes", taskID, turn+1, maxTurns, ct.Name, len(result))
|
||||
msgs = append(msgs, agentAPI.Message{Role: "assistant", Content: resp.Content, ToolCalls: []agentAPI.ToolCall{ct}})
|
||||
msgs = append(msgs, agentAPI.Message{Role: "tool", ToolCallID: ct.ID, Content: result})
|
||||
}
|
||||
@ -228,7 +228,7 @@ func (a *Agent) runChildTask(taskID, task string, parentChannel string, maxTurns
|
||||
// 只能重 spawn(日志里 child_5→6→7 就是同一任务连试三次,每次都
|
||||
// 重烧一整个上下文)。所以先做一次「不带工具的收尾调用」,把已有
|
||||
// 成果落成文本。仅在失败路径上多花一次 LLM 调用。
|
||||
log.Printf("[child] %s 跑满预算 maxTurns=%d,尝试收尾提炼已有成果", taskID, maxTurns)
|
||||
log.Printf("[child] %s exhausted budget maxTurns=%d, attempting wrap-up distillation of results so far", taskID, maxTurns)
|
||||
if summary := a.salvageChildSummary(msgs, maxTurns); summary != "" {
|
||||
finalResult = summary
|
||||
} else {
|
||||
@ -325,7 +325,7 @@ func (a *Agent) salvageChildSummary(msgs []agentAPI.Message, maxTurns int) strin
|
||||
DisableThinking: !a.thinkingEnabled,
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("[child] 收尾提炼失败(回退为事实陈述): %v", err)
|
||||
log.Printf("[child] wrap-up distillation failed (falling back to factual summary): %v", err)
|
||||
return ""
|
||||
}
|
||||
content := strings.TrimSpace(resp.Content)
|
||||
|
||||
@ -253,7 +253,7 @@ func (h *StageHost) ExecuteToolCtx(ctx context.Context, name string, args map[st
|
||||
// 而插件需要真凭据。回填是二者之间唯一的通道。
|
||||
if s := security.Global(); s != nil && args != nil {
|
||||
if n, used := s.FillMap(args); n > 0 {
|
||||
log.Printf("[security] 工具 %s 参数回填 %d 处(别名 %v)", name, n, used)
|
||||
log.Printf("[security] tool %s backfilled %d argument(s) (aliases %v)", name, n, used)
|
||||
}
|
||||
}
|
||||
// 把 ctx 放进**执行栈**而不是 handler 签名:proc 桥在自己的 CallContext
|
||||
|
||||
@ -262,7 +262,7 @@ func (a *Agent) runTaskSteps(f *TaskFrame) stepOutcome {
|
||||
// f.Turn 只在 stepTurnEnd 递增,所以它等于「已完成的工具批数」;
|
||||
// 因此这里允许 maxToolTurns 批,而不会多跑第 maxToolTurns+1 轮。
|
||||
if f.Step == StepLLM && a.maxToolTurns > 0 && f.Turn >= a.maxToolTurns {
|
||||
log.Printf("[agent] 已达最大工具轮次 %d(turn=%d),强制收尾", a.maxToolTurns, f.Turn)
|
||||
log.Printf("[agent] reached max tool turns %d (turn=%d), forcing wrap-up", a.maxToolTurns, f.Turn)
|
||||
if f.Resp != nil && strings.TrimSpace(f.Resp.Content) != "" {
|
||||
f.Response = f.Resp.Content
|
||||
} else {
|
||||
|
||||
@ -67,7 +67,7 @@ func (a *Agent) resolveToolTimeout(toolName string, args map[string]interface{})
|
||||
if d, err := time.ParseDuration(declared); err == nil && d > 0 {
|
||||
base = d
|
||||
} else if err != nil {
|
||||
log.Printf("[agent] tool %s 声明的 Timeout=%q 解析失败(%v),用默认 %v",
|
||||
log.Printf("[agent] tool %s declares Timeout=%q which failed to parse (%v), using default %v",
|
||||
toolName, declared, err, defaultToolTimeout)
|
||||
}
|
||||
}
|
||||
@ -77,7 +77,7 @@ func (a *Agent) resolveToolTimeout(toolName string, args map[string]interface{})
|
||||
}
|
||||
}
|
||||
if base > maxToolTimeout {
|
||||
log.Printf("[agent] tool %s 超时 %v 超过硬上限,夹到 %v", toolName, base, maxToolTimeout)
|
||||
log.Printf("[agent] tool %s timeout %v exceeds hard cap, clamped to %v", toolName, base, maxToolTimeout)
|
||||
return maxToolTimeout
|
||||
}
|
||||
return base
|
||||
@ -94,17 +94,17 @@ func (a *Agent) resolveToolTimeoutDeclared(toolName string) time.Duration {
|
||||
}
|
||||
d, err := time.ParseDuration(strings.TrimSpace(def.Timeout))
|
||||
if err != nil {
|
||||
log.Printf("[agent] tool %s 声明的 Timeout=%q 解析失败(%v),回退默认 %v",
|
||||
log.Printf("[agent] tool %s declares Timeout=%q which failed to parse (%v), falling back to default %v",
|
||||
toolName, def.Timeout, err, defaultToolTimeout)
|
||||
return defaultToolTimeout
|
||||
}
|
||||
if d <= 0 {
|
||||
log.Printf("[agent] tool %s 声明的 Timeout=%q 非正数,回退默认 %v",
|
||||
log.Printf("[agent] tool %s declares Timeout=%q which is not positive, falling back to default %v",
|
||||
toolName, def.Timeout, defaultToolTimeout)
|
||||
return defaultToolTimeout
|
||||
}
|
||||
if d > maxToolTimeout {
|
||||
log.Printf("[agent] tool %s 声明的 Timeout=%v 超过硬上限,夹到 %v",
|
||||
log.Printf("[agent] tool %s declares Timeout=%v exceeding hard cap, clamped to %v",
|
||||
toolName, d, maxToolTimeout)
|
||||
return maxToolTimeout
|
||||
}
|
||||
@ -187,7 +187,7 @@ func (a *Agent) executeToolCallOutcome(tc agentAPI.ToolCall, channel string, tur
|
||||
if clean, evts := st.Replace(out.Text, "tool_out:"+tc.Name, security.Patterns); len(evts) > 0 {
|
||||
out.Text = clean
|
||||
for _, e := range evts {
|
||||
log.Printf("[security] 工具输出 %s 拦截 %s(模式 %s,%d 字节)",
|
||||
log.Printf("[security] tool output %s blocked %s (pattern %s, %d bytes)",
|
||||
tc.Name, e.Alias, e.Kind, e.Len)
|
||||
}
|
||||
}
|
||||
@ -234,7 +234,7 @@ func (a *Agent) executeToolCallOutcome(tc agentAPI.ToolCall, channel string, tur
|
||||
case <-time.After(toolTimeout):
|
||||
// 先 cancel 再返回:这一行就是"让插件停下来"的触发点。
|
||||
cancel()
|
||||
log.Printf("[agent] tool %s timed out after %v(已向执行方发送取消)", tc.Name, toolTimeout)
|
||||
log.Printf("[agent] tool %s timed out after %v (cancel sent to executor)", tc.Name, toolTimeout)
|
||||
te := newToolError(ErrReasonTimeout, "", fmt.Sprintf("工具 %s 执行超时(%s)", tc.Name, fmtDuration(toolTimeout)),
|
||||
"该工具本次未在时限内返回。可改用更小的任务,或换用其他工具。")
|
||||
return toolOutcome{Text: te.Error(), Raw: te}
|
||||
@ -898,7 +898,7 @@ func (a *Agent) knowledgeMediaRefs(refs []string) []knowledge.KnowledgeMediaRef
|
||||
for _, r := range refs {
|
||||
b, ok, err := a.memory.BlockByIDPrefix(r)
|
||||
if err != nil || !ok {
|
||||
log.Printf("[knowledge] 媒体块引用 %s 无法唯一解析,跳过", r)
|
||||
log.Printf("[knowledge] media block ref %s could not be resolved uniquely, skipping", r)
|
||||
continue
|
||||
}
|
||||
out = append(out, knowledge.KnowledgeMediaRef{
|
||||
@ -1213,7 +1213,7 @@ func (a *Agent) recallByBlocks(query string) string {
|
||||
MinScore: blockRecallMinScore,
|
||||
}, query)
|
||||
if abstain != nil {
|
||||
log.Printf("[memory] abstain: 符号零命中,拒答「%s」", query)
|
||||
log.Printf("[memory] abstain: zero symbol hits, refusing to answer %q", query)
|
||||
return abstain.Notice
|
||||
}
|
||||
if err != nil || len(hits) == 0 {
|
||||
@ -1223,7 +1223,7 @@ func (a *Agent) recallByBlocks(query string) string {
|
||||
// 被取代的块不返回给模型,但**记一笔**:旧值被显式作废这件事
|
||||
// 本身有信息(「旧号 4379 停用」解释了为什么现在打不通),
|
||||
// 而完全静默会让「记忆里为什么没有旧号」变成无解之谜。
|
||||
log.Printf("[memory] recall blocks: %d 条被时序仲裁剔除(被更新的值取代)", n)
|
||||
log.Printf("[memory] recall blocks: %d removed by temporal arbitration (superseded by newer values)", n)
|
||||
}
|
||||
// 归因统计:融合到底救回了多少条 —— 这是判断它在起作用的关键读数
|
||||
var bySymbol, byExact int
|
||||
@ -1235,7 +1235,7 @@ func (a *Agent) recallByBlocks(query string) string {
|
||||
}
|
||||
}
|
||||
if byExact+bySymbol > 0 {
|
||||
log.Printf("[memory] recall blocks: 符号路补位 %d 条(精确串 %d,词法 %d)",
|
||||
log.Printf("[memory] recall blocks: symbol path backfilled %d (exact string %d, lexical %d)",
|
||||
byExact+bySymbol, byExact, bySymbol)
|
||||
}
|
||||
|
||||
@ -1357,7 +1357,7 @@ func (a *Agent) restoreDocEvents(d *document.Doc) int {
|
||||
blocks += len(held)
|
||||
}
|
||||
|
||||
log.Printf("[agent] doc→context: %s 逐条回填 %d 条原始事件(%s → %s),块 %d 个迁回 L1",
|
||||
log.Printf("[agent] doc->context: %s backfilled %d raw events one by one (%s -> %s), %d blocks migrated back to L1",
|
||||
d.ID, n,
|
||||
d.Events[0].Timestamp.Format("01-02 15:04:05"),
|
||||
d.Events[len(d.Events)-1].Timestamp.Format("01-02 15:04:05"), blocks)
|
||||
|
||||
@ -261,6 +261,19 @@ func (a *Agent) buildSystemPrompt(memContext string, userInput string) string {
|
||||
prompt += "\n\n" + a.indexer.BuildToolPrompt()
|
||||
}
|
||||
|
||||
// 【加速序列】—— 与 doc_query 同一类问题:能力存在但模型不知道何时该用。
|
||||
//
|
||||
// seq 有 7 个工具、也有 skill(seq-tools),但 skill 索引只给一行摘要。
|
||||
// 生产观察:agent 极少使用序列加速,因为它不会主动想"这件事我做过、
|
||||
// 该编成序列"或"这套操作要在多个对象上重复,该并行"。
|
||||
//
|
||||
// 这里只留**模型必须据此作决定**的判据(与其余内核块同一风格):
|
||||
// 什么时候该想起它。格式细节、组语义、常见错误全在 skill 与工具描述里,
|
||||
// 不在这里重复——重复就是两处真相。
|
||||
prompt = appendPromptBlock(prompt, "【加速序列】同一套操作在**多个对象**上重复、或几件**顺序已知**的事连做时,"+
|
||||
"先想编成序列用 `seq_run`(组内并行、组间串行);以前做过的先用 `seq_list` 找。"+
|
||||
"格式见 `seq-tools` skill。")
|
||||
|
||||
// 技能索引(方案B):轻量注入已加载技能列表,LLM 匹配到场景时
|
||||
// 主动 skill_info 拉取全文按文档执行
|
||||
if a.skillIndex != nil {
|
||||
|
||||
@ -415,8 +415,8 @@ func applyInjectOpts(payload map[string]interface{}, opts InjectOptions) {
|
||||
// 属于应当被看见的事件(见下方 log)。
|
||||
for k, v := range opts.Payload {
|
||||
if IsReservedPayloadKey(k) {
|
||||
log.Printf("[io] 注入 payload 试图覆盖内核保留键 %q(已忽略)——"+
|
||||
"该键由内核语义决定,插件不可改写", k)
|
||||
log.Printf("[io] injected payload tried to override kernel-reserved key %q (ignored) -- "+
|
||||
"this key is decided by kernel semantics and cannot be rewritten by plugins", k)
|
||||
continue
|
||||
}
|
||||
payload[k] = v
|
||||
|
||||
Reference in New Issue
Block a user