Details (original message): Implement the backend: store/bus/runmgr core, mock and LLM runners (agent-native client), HITL three-branch routing, subagent runs, builtin and declarative HTTP tools, ingest parsers (md/txt/docx/pdf-text), knowledge fulltext, layered settings, profiles, plugin host, REST+SSE API, single-binary entrypoint; e2e and unit tests.
117 lines
3.7 KiB
Go
117 lines
3.7 KiB
Go
// 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())
|
||
}
|