745b1c6aad
- iot_control: 工具描述添加明确约束,仅在用户明确要求控制时调用 - iot_query: 提示LLM设备状态已注入系统提示词,减少不必要查询 - context builder: 设备状态标题提示LLM无需调用工具查询
251 lines
7.2 KiB
Go
251 lines
7.2 KiB
Go
package context
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/yourname/cyrene-ai/ai-core/internal/memory"
|
|
"github.com/yourname/cyrene-ai/ai-core/internal/model"
|
|
"github.com/yourname/cyrene-ai/ai-core/internal/persona"
|
|
)
|
|
|
|
// IoTDeviceSummary IoT设备摘要接口(避免循环依赖)
|
|
type IoTDeviceSummary interface {
|
|
GetName() string
|
|
GetType() string
|
|
GetStatus() string
|
|
}
|
|
|
|
// ConversationStore 会话历史存储接口
|
|
type ConversationStore struct {
|
|
mu sync.RWMutex
|
|
messages map[string][]model.LLMMessage // key = sessionID
|
|
maxHistory int
|
|
}
|
|
|
|
// NewConversationStore 创建会话历史存储
|
|
func NewConversationStore(maxHistory int) *ConversationStore {
|
|
return &ConversationStore{
|
|
messages: make(map[string][]model.LLMMessage),
|
|
maxHistory: maxHistory,
|
|
}
|
|
}
|
|
|
|
// AddMessage 添加消息到会话历史
|
|
func (cs *ConversationStore) AddMessage(sessionID string, msg model.LLMMessage) {
|
|
cs.mu.Lock()
|
|
defer cs.mu.Unlock()
|
|
|
|
msgs := cs.messages[sessionID]
|
|
msgs = append(msgs, msg)
|
|
|
|
// 限制历史长度
|
|
if len(msgs) > cs.maxHistory {
|
|
// 保留 system 消息在开头,只裁剪 user/assistant 消息
|
|
cutoff := len(msgs) - cs.maxHistory
|
|
for cutoff < len(msgs) && msgs[cutoff].Role == model.RoleSystem {
|
|
cutoff++
|
|
}
|
|
if cutoff > 0 {
|
|
msgs = msgs[cutoff:]
|
|
}
|
|
}
|
|
cs.messages[sessionID] = msgs
|
|
}
|
|
|
|
// GetHistory 获取会话历史
|
|
func (cs *ConversationStore) GetHistory(sessionID string, limit int) []model.LLMMessage {
|
|
cs.mu.RLock()
|
|
defer cs.mu.RUnlock()
|
|
|
|
msgs := cs.messages[sessionID]
|
|
if len(msgs) == 0 {
|
|
return nil
|
|
}
|
|
|
|
start := 0
|
|
if limit > 0 && len(msgs) > limit {
|
|
start = len(msgs) - limit
|
|
}
|
|
|
|
result := make([]model.LLMMessage, len(msgs[start:]))
|
|
copy(result, msgs[start:])
|
|
return result
|
|
}
|
|
|
|
// Builder 对话上下文构建器
|
|
type Builder struct {
|
|
convStore *ConversationStore
|
|
}
|
|
|
|
// NewBuilder 创建上下文构建器
|
|
func NewBuilder(convStore *ConversationStore) *Builder {
|
|
return &Builder{convStore: convStore}
|
|
}
|
|
|
|
type BuildParams struct {
|
|
UserID string
|
|
SessionID string
|
|
UserMessage string
|
|
Persona *persona.PersonaConfig
|
|
Memories []memory.MemoryEntry
|
|
HistoryLimit int
|
|
DeviceContext string // 注入的设备状态文本
|
|
PendingThoughts []string // 待注入的后台思考
|
|
}
|
|
|
|
// Build 构建发送给LLM的完整消息列表
|
|
func (b *Builder) Build(ctx context.Context, params BuildParams) ([]model.LLMMessage, error) {
|
|
messages := []model.LLMMessage{}
|
|
|
|
// 1. 系统消息 —— 昔涟的人格Prompt
|
|
systemPrompt := params.Persona.BuildSystemPrompt(
|
|
params.UserID,
|
|
1,
|
|
)
|
|
|
|
// 1.1 注入设备上下文到系统消息
|
|
if params.DeviceContext != "" {
|
|
systemPrompt += "\n\n" + params.DeviceContext
|
|
}
|
|
|
|
// 1.2 注入后台思考到系统消息(不打扰地)
|
|
if len(params.PendingThoughts) > 0 {
|
|
systemPrompt += "\n\n【昔涟的内心思考(仅供你参考,不要直接复述,请自然地融入对话)】\n"
|
|
for _, thought := range params.PendingThoughts {
|
|
systemPrompt += fmt.Sprintf("- %s\n", thought)
|
|
}
|
|
}
|
|
|
|
messages = append(messages, model.LLMMessage{
|
|
Role: "system",
|
|
Content: systemPrompt,
|
|
})
|
|
|
|
// 2. 记忆注入 —— 相关记忆以系统消息形式注入
|
|
if len(params.Memories) > 0 {
|
|
memoryPrompt := "【以下是关于开拓者的一些重要记忆,请在合适的时机自然地提及】\n"
|
|
for _, m := range params.Memories {
|
|
memoryPrompt += fmt.Sprintf("- %s\n", m.Content)
|
|
}
|
|
messages = append(messages, model.LLMMessage{
|
|
Role: "system",
|
|
Content: memoryPrompt,
|
|
})
|
|
}
|
|
|
|
// 3. 历史对话
|
|
history, err := b.loadHistory(ctx, params.SessionID, params.HistoryLimit)
|
|
if err == nil {
|
|
messages = append(messages, history...)
|
|
}
|
|
|
|
// 4. 当前用户消息
|
|
messages = append(messages, model.LLMMessage{
|
|
Role: "user",
|
|
Content: params.UserMessage,
|
|
})
|
|
|
|
return messages, nil
|
|
}
|
|
|
|
// loadHistory 从 ConversationStore 加载会话历史
|
|
func (b *Builder) loadHistory(_ context.Context, sessionID string, limit int) ([]model.LLMMessage, error) {
|
|
if b.convStore == nil {
|
|
log.Printf("[context] 会话历史存储未初始化,跳过加载")
|
|
return nil, nil
|
|
}
|
|
|
|
history := b.convStore.GetHistory(sessionID, limit)
|
|
if len(history) == 0 {
|
|
log.Printf("[context] 会话 %s 无历史记录", sessionID)
|
|
return nil, nil
|
|
}
|
|
|
|
log.Printf("[context] 加载会话 %s 历史 %d 条", sessionID, len(history))
|
|
return history, nil
|
|
}
|
|
|
|
// CacheMessage 缓存消息到会话历史(供chat handler在回复后调用)
|
|
func (b *Builder) CacheMessage(sessionID string, role model.Role, content string) {
|
|
if b.convStore == nil {
|
|
return
|
|
}
|
|
b.convStore.AddMessage(sessionID, model.LLMMessage{
|
|
Role: role,
|
|
Content: content,
|
|
})
|
|
}
|
|
|
|
// InjectDeviceContext 将设备状态格式化为简洁的文本注入系统上下文
|
|
func InjectDeviceContext(devices []DeviceInfo) string {
|
|
if len(devices) == 0 {
|
|
return ""
|
|
}
|
|
|
|
var sb strings.Builder
|
|
sb.WriteString("[当前IoT设备状态 — 你已知晓这些设备的状态,无需调用工具查询,直接引用即可]\n")
|
|
for _, d := range devices {
|
|
switch d.Type {
|
|
case "light":
|
|
if d.Status == "on" {
|
|
sb.WriteString(fmt.Sprintf("- %s: 开启 (亮度%d%%, %s)\n", d.Name, d.Brightness, d.Color))
|
|
} else {
|
|
sb.WriteString(fmt.Sprintf("- %s: 关闭\n", d.Name))
|
|
}
|
|
case "ac":
|
|
if d.Status == "on" {
|
|
modeLabel := acModeLabel(d.Mode)
|
|
sb.WriteString(fmt.Sprintf("- %s: 运行中 (%s%.0f°C)\n", d.Name, modeLabel, d.Temperature))
|
|
} else {
|
|
sb.WriteString(fmt.Sprintf("- %s: 关闭\n", d.Name))
|
|
}
|
|
case "curtain":
|
|
statusLabel := "已关闭"
|
|
if d.Status == "open" {
|
|
statusLabel = "已打开"
|
|
}
|
|
sb.WriteString(fmt.Sprintf("- %s: %s\n", d.Name, statusLabel))
|
|
case "sensor":
|
|
sb.WriteString(fmt.Sprintf("- %s: %.1f%s\n", d.Name, d.Value, d.Unit))
|
|
case "lock":
|
|
statusLabel := "已锁定"
|
|
if d.Status == "unlocked" {
|
|
statusLabel = "已解锁"
|
|
}
|
|
sb.WriteString(fmt.Sprintf("- %s: %s (电量%d%%)\n", d.Name, statusLabel, d.Battery))
|
|
}
|
|
}
|
|
return sb.String()
|
|
}
|
|
|
|
// DeviceInfo 设备信息(避免循环依赖的简化结构体)
|
|
type DeviceInfo struct {
|
|
Name string
|
|
Type string
|
|
Status string
|
|
Brightness int
|
|
Color string
|
|
Temperature float64
|
|
Mode string
|
|
Value float64
|
|
Unit string
|
|
Battery int
|
|
}
|
|
|
|
func acModeLabel(mode string) string {
|
|
switch mode {
|
|
case "cool":
|
|
return "制冷"
|
|
case "heat":
|
|
return "制热"
|
|
case "auto":
|
|
return "自动"
|
|
default:
|
|
return mode
|
|
}
|
|
}
|