feat(agent): 重构随口记为模型控制码分流 + 单请求聚合规划,关闭非流式thinking并修复假成功,将随口记全流程从10s+缩短到5s左右,显著提升用户体验 路由层改为“模型控制码协议”分流(quick_note|chat),替换关键词/置信度猜测 路由命中 quick_note 时信任路由,graph 跳过二次意图判定(减少一次 LLM 调用) 新增单请求聚合规划:一次返回 title/deadline_at/priority_group/priority_reason/banter 快路径优先复用聚合结果;优先级缺失时本地兜底,避免再次触发优先级模型调用 最终回复优先使用聚合 banter,聚合路径缺失时使用固定文案,不再额外润色调用 非流式 Generate 全面显式关闭 thinking,并收紧 max_tokens/temperature(路由、JSON规划、banter) 保留并强化写库成功门槛:task_id > 0 才允许成功回包,修复“回复成功但未落库”风险 增加/更新测试:控制码解析、nonce 校验、标题提取、banter 复用与无效 task_id 防假成功 保持 OpenAI 兼容 SSE 格式与现有流式聊天链路不变
73 lines
2.7 KiB
Go
73 lines
2.7 KiB
Go
package service
|
||
|
||
import (
|
||
"strings"
|
||
"testing"
|
||
|
||
"github.com/LoveLosita/smartflow/backend/agent"
|
||
)
|
||
|
||
// TestParseQuickNoteRouteControlTag_QuickNote
|
||
// 目的:验证模型控制码在 action=quick_note 时可被稳定解析,
|
||
// 并且会校验 nonce,避免历史脏内容或伪造片段误命中。
|
||
func TestParseQuickNoteRouteControlTag_QuickNote(t *testing.T) {
|
||
nonce := "abc123nonce"
|
||
raw := `<SMARTFLOW_ROUTE nonce="abc123nonce" action="quick_note"></SMARTFLOW_ROUTE>
|
||
<SMARTFLOW_REASON>用户明确在请求未来提醒</SMARTFLOW_REASON>`
|
||
|
||
decision, err := parseQuickNoteRouteControlTag(raw, nonce)
|
||
if err != nil {
|
||
t.Fatalf("解析失败: %v", err)
|
||
}
|
||
if decision == nil {
|
||
t.Fatalf("decision 不应为空")
|
||
}
|
||
if decision.Action != quickNoteRouteActionQuickNote {
|
||
t.Fatalf("action 解析错误,期望=%s 实际=%s", quickNoteRouteActionQuickNote, decision.Action)
|
||
}
|
||
if strings.TrimSpace(decision.Reason) == "" {
|
||
t.Fatalf("reason 不应为空")
|
||
}
|
||
}
|
||
|
||
// TestParseQuickNoteRouteControlTag_NonceMismatch
|
||
// 目的:确保 nonce 不匹配时直接报错,避免把非本次请求的控制码当作有效路由。
|
||
func TestParseQuickNoteRouteControlTag_NonceMismatch(t *testing.T) {
|
||
raw := `<SMARTFLOW_ROUTE nonce="wrongnonce" action="chat"></SMARTFLOW_ROUTE>`
|
||
if _, err := parseQuickNoteRouteControlTag(raw, "expectednonce"); err == nil {
|
||
t.Fatalf("期望 nonce 不匹配时报错,但未报错")
|
||
}
|
||
}
|
||
|
||
// TestBuildQuickNoteFinalReply_NoFalseSuccessWithoutTaskID
|
||
// 目的:即使 state.Persisted 被错误置为 true,只要 task_id 无效,也不能返回“安排成功”文案。
|
||
func TestBuildQuickNoteFinalReply_NoFalseSuccessWithoutTaskID(t *testing.T) {
|
||
state := &agent.QuickNoteState{
|
||
Persisted: true,
|
||
PersistedTaskID: 0,
|
||
ExtractedTitle: "去下馆子",
|
||
}
|
||
|
||
reply := buildQuickNoteFinalReply(nil, nil, "我今天晚上6点要去下馆子,记得喊我", state)
|
||
if strings.Contains(reply, "给你安排上了") || strings.Contains(reply, "已安排") {
|
||
t.Fatalf("不应返回成功文案,实际回复=%s", reply)
|
||
}
|
||
}
|
||
|
||
// TestBuildQuickNoteFinalReply_UseExtractedBanter
|
||
// 目的:当聚合规划阶段已经产出 banter 时,最终回复应直接复用,避免再次调用润色模型。
|
||
func TestBuildQuickNoteFinalReply_UseExtractedBanter(t *testing.T) {
|
||
state := &agent.QuickNoteState{
|
||
Persisted: true,
|
||
PersistedTaskID: 12,
|
||
ExtractedTitle: "明天去取快递",
|
||
ExtractedPriority: 2,
|
||
ExtractedBanter: "取件路上注意保暖,别被风吹懵了。",
|
||
}
|
||
|
||
reply := buildQuickNoteFinalReply(nil, nil, "明天上午12点我要去取快递,到时候记得q我", state)
|
||
if !strings.Contains(reply, "取件路上注意保暖") {
|
||
t.Fatalf("期望复用 ExtractedBanter,实际回复=%s", reply)
|
||
}
|
||
}
|