Files
Agent-Native/runtime/llm/prepare.go
T
weichen 3a110a728c
ci / vet-and-test (pull_request) Successful in 51s
ci / docker-checkout-hostgateway (pull_request) Successful in 1s
Shape provider messages before they are sent
Why: Some endpoints reject consecutive system messages or a run of the same role. The product already emits several system messages in one request.
Evidence: go test is covered by PrepareMessages tests for merge, strict folding, and tool pairs. Empty MessageShape leaves the transcript unchanged.
Impact: Callers opt in with StreamOptions.MessageShape. Tool calls and tool results are not merged.
Refs: docs/records/2026-10-04-sillytavern-review.md
2026-10-04 18:18:57 +00:00

61 lines
1.5 KiB
Go

package llm
// PrepareMessages 按 endpoint 整形消息。
// merge 合并相邻、且不携带工具调用的同角色消息。
// strict 在此之后把第一条之后的 system 收成 user,再合并,满足严格交替。
// 工具消息和带 tool_calls 的助手消息保持原样,避免拆开调用与结果。
func PrepareMessages(msgs []Message, shape string) []Message {
switch shape {
case "", "merge", "strict":
default:
return msgs
}
out := mergeSameRole(msgs)
if shape == "strict" {
out = foldExtraSystems(out)
out = mergeSameRole(out)
}
return out
}
func mergeable(m Message) bool {
return m.Role != RoleTool && len(m.ToolCalls) == 0 && m.ToolCallID == ""
}
func mergeSameRole(msgs []Message) []Message {
if len(msgs) == 0 {
return msgs
}
out := make([]Message, 0, len(msgs))
for _, m := range msgs {
if n := len(out); n > 0 && out[n-1].Role == m.Role && mergeable(out[n-1]) && mergeable(m) {
if out[n-1].Content != "" && m.Content != "" {
out[n-1].Content += "\n\n" + m.Content
} else {
out[n-1].Content += m.Content
}
continue
}
out = append(out, m)
}
return out
}
func foldExtraSystems(msgs []Message) []Message {
seenSystem := false
out := make([]Message, 0, len(msgs))
for _, m := range msgs {
if m.Role == RoleSystem {
if seenSystem {
m.Role = RoleUser
if m.Content != "" {
m.Content = "[system]\n" + m.Content
}
}
seenSystem = true
}
out = append(out, m)
}
return out
}