// Package ctxasm 实现上下文组装与预算管理(4.10):分层管线 + 可观测裁剪记录。 package ctxasm import ( "fmt" "strings" agentllm "agent-native/runtime/llm" core "agent-system-core" ) // Report 记录一次组装的实际构成(可观测:模型看到了什么、裁了什么)。 type Report struct { Items []string `json:"items"` Truncations []string `json:"truncations,omitempty"` EstChars int `json:"estChars"` } // AssembleInput 组装模型输入消息。 func AssembleInput(prof core.AgentProfile, eff core.EffectiveSettings, history []core.Message, currentUser core.Message, attachments []core.ParsedDocument, knowledge []core.RetrievalHit) ([]agentllm.Message, *Report) { rep := &Report{} msgs := []agentllm.Message{{Role: agentllm.RoleSystem, Content: prof.SystemPrompt}} rep.Items = append(rep.Items, "system(画像)") maxHist := prof.Context.MaxHistoryMessages if maxHist <= 0 { maxHist = int(eff.GetNumber("context.maxHistoryMessages", 40)) } if maxHist <= 0 { maxHist = 40 } // 历史窗口(不含当前轮——当前轮单独注入附件与检索)。 start := 0 if len(history) > maxHist { start = len(history) - maxHist rep.Truncations = append(rep.Truncations, fmt.Sprintf("历史 %d 条超窗口 %d,仅保留最近部分", len(history), maxHist)) } for _, m := range history[start:] { if m.ID == currentUser.ID { continue } content := renderMessage(m) if content == "" { continue } role := agentllm.RoleUser if m.Role == core.RoleAssistant { role = agentllm.RoleAssistant } msgs = append(msgs, agentllm.Message{Role: role, Content: content}) } rep.Items = append(rep.Items, fmt.Sprintf("history(%d)", len(history)-start)) // 当前轮:文本 + 附件解析结果 + 知识检索注入。 var sb strings.Builder for _, p := range currentUser.Parts { if p.Type == core.PartText { sb.WriteString(p.Text) } } if len(attachments) > 0 { sb.WriteString("\n\n[附件]\n") for _, doc := range attachments { text := doc.PlainText() limit := 16000 if len(text) > limit { text = text[:limit] + "…(附件超长截断)" rep.Truncations = append(rep.Truncations, "附件 "+doc.Meta.Filename+" 超长截断") } fmt.Fprintf(&sb, "### %s\n%s\n", doc.Meta.Filename, text) } rep.Items = append(rep.Items, fmt.Sprintf("attachments(%d)", len(attachments))) } if len(knowledge) > 0 { sb.WriteString("\n\n[相关知识]\n") for i, h := range knowledge { fmt.Fprintf(&sb, "[%d] %s(来源 %s)\n%s\n", i+1, h.Doc.Title, h.Doc.Source, h.Doc.Chunk) } rep.Items = append(rep.Items, fmt.Sprintf("knowledge(%d)", len(knowledge))) } msgs = append(msgs, agentllm.Message{Role: agentllm.RoleUser, Content: sb.String()}) for _, m := range msgs { rep.EstChars += len(m.Content) } return msgs, rep } // renderMessage 把消息部件渲染为模型可读文本(思考部件不回灌;工具结果给摘要)。 func renderMessage(m core.Message) string { var sb strings.Builder for _, p := range m.Parts { switch p.Type { case core.PartText: sb.WriteString(p.Text) sb.WriteString("\n") case core.PartToolCall: if p.ToolCall != nil { fmt.Fprintf(&sb, "[调用工具 %s(%s)]\n", p.ToolCall.Name, p.ToolCall.Arguments) } case core.PartToolResult: if p.ToolResult != nil { fmt.Fprintf(&sb, "[工具结果 %s %s]\n", p.ToolResult.Status, p.ToolResult.Summary) } case core.PartArtifactRef: if p.Artifact != nil { fmt.Fprintf(&sb, "[产出工件 %s]\n", p.Artifact.Name) } case core.PartHITL: if p.Hitl != nil && p.Hitl.Resolved { fmt.Fprintf(&sb, "[用户确认:%s → %s]\n", p.Hitl.Question, p.Hitl.Answer) } } } return strings.TrimSpace(sb.String()) }