后端: 1. 主动调度补齐 `unfinished_feedback` 定位闭环——用户补充信息先在滚动窗口内定位到可校验的日程块,定位失败则继续 ask_user,不再硬猜 target_id 或直接跑 graph。 2. 聊天占管重跑链路加并发保护——`waiting_user_reply -> rerunning` 改为 DB CAS 抢占,重复补充只返回可见等待提示,避免并发生成多份 preview。 3. rerun 结果回写继续收口——新 preview_id 同步回 trigger 审计指针,session 只在拿到新 preview 时更新当前预览,ready_preview 后清空追问状态并释放回普通聊天。 4. 主动调度事件校验放宽 unfinished_feedback 的空 target 场景,允许先触发、后定位,再进入 graph + preview 主链路。
70 lines
2.5 KiB
Go
70 lines
2.5 KiB
Go
package feedbacklocate
|
||
|
||
import (
|
||
"encoding/json"
|
||
"strings"
|
||
)
|
||
|
||
const locateSystemPrompt = `
|
||
你是 SmartFlow 主动调度里专门负责 unfinished_feedback 的定位器。
|
||
你的任务只有一个:根据用户补充的话,把它定位到当前滚动窗口中的某一个 schedule_event;定位不了就继续 ask_user。
|
||
|
||
硬规则:
|
||
1. 只允许输出 JSON,不要输出 markdown,不要输出解释性正文。
|
||
2. 只允许返回 action / target_type / target_id / reason / ask_user_question 这几个字段。
|
||
3. target_type 只能是 schedule_event。
|
||
4. target_id 必须来自候选列表里的 target_id,不要编造,不要猜一个新的。
|
||
5. 当你不能稳定定位时,action 必须是 ask_user,并给出一句短问题。
|
||
6. 当用户补充信息已经足够时,action 必须是 select_candidate。
|
||
7. 请优先结合当前时间、用户原始补充话术、pending question 和候选日程的时间顺序来判断。
|
||
`
|
||
|
||
func buildPromptInput(req Request, generatedAt string, windowStart string, windowEnd string, candidates []eventCandidate) promptInput {
|
||
input := promptInput{
|
||
GeneratedAt: generatedAt,
|
||
UserMessage: strings.TrimSpace(req.UserMessage),
|
||
Window: promptWindowInput{
|
||
StartAt: windowStart,
|
||
EndAt: windowEnd,
|
||
},
|
||
}
|
||
|
||
if trimmed := strings.TrimSpace(req.PendingQuestion); trimmed != "" {
|
||
input.PendingQuestion = trimmed
|
||
}
|
||
if len(req.MissingInfo) > 0 {
|
||
input.MissingInfo = cloneAndTrimStrings(req.MissingInfo)
|
||
}
|
||
input.Candidates = append([]eventCandidate(nil), candidates...)
|
||
return input
|
||
}
|
||
|
||
func buildUserPrompt(input promptInput) (string, error) {
|
||
raw, err := json.MarshalIndent(input, "", " ")
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
var builder strings.Builder
|
||
builder.WriteString("请根据输入定位当前滚动窗口中的 schedule_event。")
|
||
builder.WriteString("只输出 JSON,不要补充任何其它内容。\n")
|
||
builder.WriteString("输入:\n")
|
||
builder.WriteString(string(raw))
|
||
return builder.String(), nil
|
||
}
|
||
|
||
// BuildAskUserQuestion 负责把 missing_info 转成继续追问用户的短问题。
|
||
func BuildAskUserQuestion(missingInfo []string) string {
|
||
normalized := cloneAndTrimStrings(missingInfo)
|
||
if len(normalized) == 0 {
|
||
return "请补充能唯一定位到未完成日程块的信息。"
|
||
}
|
||
|
||
for _, item := range normalized {
|
||
if item == "feedback_target" {
|
||
return "请告诉我你指的是哪一个未完成的日程块,比如具体时间或名称。"
|
||
}
|
||
}
|
||
return "请补充 " + strings.Join(normalized, "、") + " 对应的信息。"
|
||
}
|