mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-05 13:29:44 +02:00
The Lark short-window debounce (MUL-2968, #3742) can land several user messages in a chat session before a single agent run fires. But the daemon claim built the agent prompt from only the *single most recent* user message (walk history backward, take first user message, break). So 「看上海天气」then「还有青岛」debounced into one run, and the agent received only 「还有青岛」— it answered Qingdao and never saw Shanghai. The session itself was correct (both messages persisted); the gap was in what the run delivered to the agent. Before debouncing this was masked because each message got its own run. Build the prompt from the whole unanswered set instead: the trailing run of user messages after the last assistant reply (every completed/failed run writes an assistant row, so the anchor advances one turn at a time — the full burst on the first turn, only the new message(s) after a reply). Attachments are collected from each included message. Extracted the selection into a pure trailingUserMessages helper with table-driven unit tests, plus a DB-backed claim test asserting both messages reach the agent and that a post-reply message delivers alone. Co-authored-by: J <j@multica.ai> Co-authored-by: multica-agent <github@multica.ai>
89 lines
2.1 KiB
Go
89 lines
2.1 KiB
Go
package handler
|
|
|
|
import (
|
|
"testing"
|
|
|
|
db "github.com/multica-ai/multica/server/pkg/db/generated"
|
|
)
|
|
|
|
func msg(role, content string) db.ChatMessage {
|
|
return db.ChatMessage{Role: role, Content: content}
|
|
}
|
|
|
|
func contents(msgs []db.ChatMessage) []string {
|
|
out := make([]string, len(msgs))
|
|
for i, m := range msgs {
|
|
out[i] = m.Content
|
|
}
|
|
return out
|
|
}
|
|
|
|
func eq(a, b []string) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i := range a {
|
|
if a[i] != b[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// TestTrailingUserMessages pins the message-selection logic behind the daemon
|
|
// chat prompt: the agent must receive every user message since its last reply
|
|
// (the MUL-2968 debounce can land several before one run fires), not just the
|
|
// most recent one.
|
|
func TestTrailingUserMessages(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
in []db.ChatMessage
|
|
want []string
|
|
}{
|
|
{
|
|
name: "debounced burst with no prior reply delivers all",
|
|
in: []db.ChatMessage{msg("user", "看上海天气"), msg("user", "还有青岛")},
|
|
want: []string{"看上海天气", "还有青岛"},
|
|
},
|
|
{
|
|
name: "only messages after the last assistant reply",
|
|
in: []db.ChatMessage{
|
|
msg("user", "old q"), msg("assistant", "old a"),
|
|
msg("user", "看上海天气"), msg("user", "还有青岛"),
|
|
},
|
|
want: []string{"看上海天气", "还有青岛"},
|
|
},
|
|
{
|
|
name: "single new message after a reply",
|
|
in: []db.ChatMessage{
|
|
msg("user", "看上海天气"), msg("user", "还有青岛"),
|
|
msg("assistant", "weather…"), msg("user", "深圳呢"),
|
|
},
|
|
want: []string{"深圳呢"},
|
|
},
|
|
{
|
|
name: "no trailing user message (last is assistant)",
|
|
in: []db.ChatMessage{msg("user", "hi"), msg("assistant", "done")},
|
|
want: []string{},
|
|
},
|
|
{
|
|
name: "empty history",
|
|
in: []db.ChatMessage{},
|
|
want: []string{},
|
|
},
|
|
{
|
|
name: "single user message",
|
|
in: []db.ChatMessage{msg("user", "hi")},
|
|
want: []string{"hi"},
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got := contents(trailingUserMessages(tc.in))
|
|
if !eq(got, tc.want) {
|
|
t.Fatalf("trailingUserMessages = %v, want %v", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|