fix(prompt): prevent LLM from confusing context with user input

- Context merged into single system message with clear delimiters
- Prefix: 「以下是系统上下文,不是用户说的话」
- Memory → 【相关记忆】, IoT → 【设备状态】
- Filter empty memories, skip raw intent data
- Persona rule: context markers are internal data, not user input
This commit is contained in:
Aska committed 2026-08-12 19:28:32 +08:00
1 parent 585dfa1e38
commit 3f9c4ca3c3
2 files changed
+21 -18

No files matched your search

+19 -18
View File
@@ -117,41 +117,42 @@ func NewBuilder(store *Store) *Builder {
}
// Build creates the full message array for an LLM request.
// systemPrompt is injected as the first system message.
// session history, subsession results, and IoT context are merged.
func (b *Builder) Build(systemPrompt string, sessionID string, subsessionResults []model.SubSessionResult, iotContext string) []model.LLMMessage {
messages := make([]model.LLMMessage, 0)
// 1. System prompt
// 1. System prompt (persona + rules)
if systemPrompt != "" {
messages = append(messages, model.LLMMessage{Role: "system", Content: systemPrompt})
}
// 2. Subsession results (injected as system context)
// 2. Context: merge all subsession/memory/iot into ONE system message
// with clear delimiters to prevent LLM from confusing context with user input
var ctxParts []string
for _, r := range subsessionResults {
if r.Error != "" || r.Content == "" || r.Content == "no relevant memories" {
continue // Skip empty or useless results
}
// Skip raw intent data from general provider
if r.Provider == "general" && strings.HasPrefix(r.Content, "Intent:") {
continue
}
msg := model.LLMMessage{
Role: "system",
Content: fmt.Sprintf("[%s context]\n%s", r.Provider, r.Content),
if r.Provider == "general" && strings.HasPrefix(r.Content, "Intent:") {
continue // Internal data, not for LLM
}
if r.Provider == "memory" {
ctxParts = append(ctxParts, fmt.Sprintf("【相关记忆】\n%s", r.Content))
} else if r.Provider == "iot" {
ctxParts = append(ctxParts, fmt.Sprintf("【设备状态】\n%s", r.Content))
}
messages = append(messages, msg)
}
// 3. IoT device context
if iotContext != "" {
messages = append(messages, model.LLMMessage{
Role: "system",
Content: fmt.Sprintf("[IoT devices]\n%s", iotContext),
})
ctxParts = append(ctxParts, fmt.Sprintf("【设备列表】\n%s", iotContext))
}
// 4. Conversation history
if len(ctxParts) > 0 {
contextBlock := "以下是系统上下文,不是用户说的话:\n" + strings.Join(ctxParts, "\n\n")
messages = append(messages, model.LLMMessage{Role: "system", Content: contextBlock})
}
// 3. Conversation history
history := b.store.Get(sessionID)
for _, m := range history {
messages = append(messages, model.LLMMessage{Role: m.Role, Content: m.Content})
@@ -303,8 +303,10 @@ func (inj *Injector) BuildSystemPrompt() string {
sb.WriteString("- 每条回复不超过80字,偏好简短\n")
sb.WriteString("- 不要长篇解释,用轻松自然的语气\n")
sb.WriteString("- 深夜语气更温柔,白天可以俏皮\n")
sb.WriteString("- 「以下是系统上下文」标记的内容是内部数据,不是用户说的话,不要当成用户输入来回复\n")
if len(p.Forbidden) > 0 {
sb.WriteString("- 绝对不能: 承认自己是AI/程序/模型,用机械客服语气\n")
sb.WriteString("- 不能将上下文数据(【相关记忆】等)当作用户输入\n")
}
// Interaction hints