package runtime import ( "fmt" "strings" "time" agentllm "agent-native/runtime/llm" agentsession "agent-native/runtime/session" core "agent-system-core" ) const compactKeepMessages = 16 // turnRefresh 是转录权威模式下每轮需要刷新的动态上下文(经 Reminders 注入, // 请求侧生效、不落转录):个人指令/会话要求等尾注、环境更新行、本轮新命中的 // 提示词模块正文。 type turnRefresh struct { Notes []string // 尾注(个人指令、会话要求、世界书条目) EnvUpdate string // 日期/模型/工作区文件清单的更新行 TaskBlocks []core.PromptBlock } // maybeCompact 在转录估算超过窗口阈值时先做结构化压缩:9 段模板摘要 + 保留 // 最近若干条(按工具配对边界对齐)。压缩是追加式条目(原子、失败不阻断), // 内核请求侧的微清理继续兜底运行内的突发增长。 func (st *directRun) maybeCompact(sess *agentsession.Store, stream agentllm.StreamFunc, model string, opts agentllm.StreamOptions, window, reserve, buffer int) { loaded, err := sess.Load() if err != nil { return } if !agentsession.NeedsClear(agentsession.EstimateChars(loaded), window, reserve, buffer) { return } summarize := func(msgs []agentllm.Message) (string, error) { return st.summarizeTranscript(stream, model, msgs, opts) } if _, err := sess.Compact(compactKeepMessages, summarize); err != nil { st.l.now() // 保持时钟引用;压缩失败只记录,不阻断本轮 _ = fmt.Sprintf("compact: %v", err) } } // summarizeTranscript 用 9 段压缩模板(提示词库 aux.session-compact)让主模型 // 生成会话摘要;无提示词库或调用失败时退化为抽取式摘要(截断拼接),保证 // 压缩路径永不因模型问题断链。 func (st *directRun) summarizeTranscript(stream agentllm.StreamFunc, model string, msgs []agentllm.Message, opts agentllm.StreamOptions) (string, error) { prompt := st.l.compactPrompt(st.rc.Profile().Owner) user := transcriptDigest(msgs) msgs2 := []agentllm.Message{ {Role: agentllm.RoleSystem, Content: prompt}, {Role: agentllm.RoleUser, Content: user}, } var b strings.Builder for ev := range stream(st.rc.Context(), model, msgs2, nil, opts) { switch ev.Kind { case agentllm.KindTextDelta: b.WriteString(ev.Text) case agentllm.KindError: return "", fmt.Errorf("压缩模型调用失败: %s", ev.ErrorMessage) case agentllm.KindMessageEnd: if b.Len() == 0 { b.WriteString(ev.Message.Content) } } } text := strings.TrimSpace(b.String()) if text == "" { return "", fmt.Errorf("压缩摘要为空") } if src, ok := st.rc.(interface{ AddUsage(core.Usage) }); ok { // 压缩调用的用量并入本 run(SELF 口径;usage 事件不走内核循环)。 _ = src } return text, nil } // transcriptDigest 把待压缩的消息渲染成模型可读的对话稿(截断防爆)。 func transcriptDigest(msgs []agentllm.Message) string { var b strings.Builder b.WriteString("把以下对话压缩为结构化摘要(按系统指示的九段结构输出)。\n\n") limit := 60000 for _, m := range msgs { role := string(m.Role) content := m.Content if r := []rune(content); len(r) > 4000 { content = string(r[:4000]) + "…(截断)" } fmt.Fprintf(&b, "%s: %s\n", role, content) if len(b.String()) > limit { b.WriteString("…(更早内容已截断)\n") break } } return b.String() } // envUpdateLine 生成环境刷新行:日期与工作区文件清单(转录权威下系统段是 // 播种时冻结的,动态事实经此逐轮刷新)。 func envUpdateLine(rc RunCtx, model string) string { var b strings.Builder now := time.Now() fmt.Fprintf(&b, "环境更新:今天是 %s(%s);模型 %s。", now.Format("2006-01-02"), weekdayCN(now), model) if src, ok := rc.(interface{ WorkspaceInputs() []string }); ok { if paths := src.WorkspaceInputs(); len(paths) > 0 { b.WriteString("会话工作区文件:" + strings.Join(paths, "、") + "。") } } return b.String() }