feat: Phase 1+2 架构进化 — 连续思考链/主动消息决策/情感状态机/离线自主思考 (86文件)
Phase 1 (基础设施): - ThinkChain 思考链连续性 + 差异化思考提示词 (persistent) - AutonomousToolPolicy 工具安全策略 (safe/unsafe/conditional) - MessageScheduler 自适应消息节奏 (Idle/Available/Busy) - SessionEnrichmentStore 渐进式上下文丰富 (5层) - ConversationBus 事件总线 + ResponseCache (dedup) - pkg/logger 统一日志 + 所有 handler 替换 fmt.Printf - NPE 守卫/链路优化/数据库表修复/Go workspace Phase 2 (人格交互): - EmotionState/EmotionTracker 情感状态机 (5种心情, 情绪衰减) - ProactiveGuard 主动消息多维决策 (静默时段/紧急度/频率/校验) - Gateway↔ai-core 在线状态感知链路 (presence notification) - 离线思考频率控制 + 重连问候 + 离线消息排队 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@ package subsession
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"github.com/yourname/cyrene-ai/pkg/logger"
|
||||
"time"
|
||||
|
||||
"github.com/yourname/cyrene-ai/ai-core/internal/llm"
|
||||
@@ -29,8 +29,10 @@ func (p *GeneralProvider) Type() model.SubSessionType {
|
||||
}
|
||||
|
||||
func (p *GeneralProvider) CanHandle(_ context.Context, _ *model.IntentResult, _ string) bool {
|
||||
// General 子会话总是需要(核心对话逻辑)
|
||||
return true
|
||||
// Phase 1 Step 2: GeneralProvider is a no-op (Execute returns hardcoded string).
|
||||
// Chat synthesis is handled directly by the orchestrator's Synthesizer.
|
||||
// Disabled to avoid wasting a goroutine + LLM context creation.
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *GeneralProvider) Priority() int {
|
||||
@@ -123,7 +125,7 @@ func (p *GeneralProvider) Execute(ctx context.Context, subCtx []model.LLMMessage
|
||||
// 由于 GeneralProvider 暂时不需要工具调用等特殊逻辑,我们返回一个简单的摘要标记,
|
||||
// 实际的 LLM 调用将在 orchestrator 中完成(通过 Manager.Dispatch 后的 llmClient)。
|
||||
|
||||
log.Printf("[general-subsession] 通用对话子会话上下文已创建 (%d 条消息)", len(subCtx))
|
||||
logger.Printf("[general-subsession] 通用对话子会话上下文已创建 (%d 条消息)", len(subCtx))
|
||||
return &model.SubSessionResult{
|
||||
Type: model.SubSessionGeneral,
|
||||
Summary: "思考完成,等待主会话综合",
|
||||
|
||||
@@ -3,7 +3,7 @@ package subsession
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"github.com/yourname/cyrene-ai/pkg/logger"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -121,7 +121,7 @@ func (p *IoTProvider) CreateContext(ctx context.Context, params CreateContextPar
|
||||
}
|
||||
loader, err := persona.NewLoader(personaPath)
|
||||
if err != nil {
|
||||
log.Printf("[iot-provider] 加载人格配置失败: %v", err)
|
||||
logger.Printf("[iot-provider] 加载人格配置失败: %v", err)
|
||||
}
|
||||
if loader != nil {
|
||||
if personaConfig, err := loader.Get("cyrene"); err == nil && personaConfig != nil {
|
||||
@@ -203,16 +203,16 @@ func (p *IoTProvider) Execute(ctx context.Context, subCtx []model.LLMMessage) (*
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[iot-provider] 📥 开始处理 IoT 子会话: userMessage=%s", truncateStr(userMessage, 80))
|
||||
logger.Printf("[iot-provider] 📥 开始处理 IoT 子会话: userMessage=%s", truncateStr(userMessage, 80))
|
||||
|
||||
if p.iotClient == nil {
|
||||
log.Printf("[iot-provider] ⚠️ IoT 客户端未配置,无法控制设备")
|
||||
logger.Printf("[iot-provider] ⚠️ IoT 客户端未配置,无法控制设备")
|
||||
result.Summary = "(IoT 客户端未配置,无法控制设备)"
|
||||
return result, nil
|
||||
}
|
||||
|
||||
devices := p.iotClient.GetDevicesForContext(ctx)
|
||||
log.Printf("[iot-provider] 📋 获取到 %d 个设备用于匹配", len(devices))
|
||||
logger.Printf("[iot-provider] 📋 获取到 %d 个设备用于匹配", len(devices))
|
||||
|
||||
msgLower := strings.ToLower(userMessage)
|
||||
userName := extractUserName(subCtx)
|
||||
@@ -274,7 +274,7 @@ func (p *IoTProvider) Execute(ctx context.Context, subCtx []model.LLMMessage) (*
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
log.Printf("[iot-provider] ❌ 未匹配到 IoT 操作: userMessage=%s", truncateStr(userMessage, 80))
|
||||
logger.Printf("[iot-provider] ❌ 未匹配到 IoT 操作: userMessage=%s", truncateStr(userMessage, 80))
|
||||
result.Summary = "(未匹配到 IoT 操作)"
|
||||
result.Confidence = 0.5
|
||||
return result, nil
|
||||
@@ -300,7 +300,7 @@ func (p *IoTProvider) Execute(ctx context.Context, subCtx []model.LLMMessage) (*
|
||||
Arguments: map[string]any{"device_id": action.dev.ID, "operation": "toggle"},
|
||||
Result: "success",
|
||||
})
|
||||
log.Printf("[iot-subsession] 执行操作: 打开 %s (%s)", action.dev.Name, action.dev.ID)
|
||||
logger.Printf("[iot-subsession] 执行操作: 打开 %s (%s)", action.dev.Name, action.dev.ID)
|
||||
executedCount++
|
||||
} else {
|
||||
summaries = append(summaries, fmt.Sprintf("%s已经是打开状态啦~", action.dev.Name))
|
||||
@@ -318,7 +318,7 @@ func (p *IoTProvider) Execute(ctx context.Context, subCtx []model.LLMMessage) (*
|
||||
Arguments: map[string]any{"device_id": action.dev.ID, "operation": "toggle"},
|
||||
Result: "success",
|
||||
})
|
||||
log.Printf("[iot-subsession] 执行操作: 关闭 %s (%s)", action.dev.Name, action.dev.ID)
|
||||
logger.Printf("[iot-subsession] 执行操作: 关闭 %s (%s)", action.dev.Name, action.dev.ID)
|
||||
executedCount++
|
||||
} else {
|
||||
summaries = append(summaries, fmt.Sprintf("%s已经是关闭状态啦~", action.dev.Name))
|
||||
|
||||
@@ -4,9 +4,10 @@ import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"log"
|
||||
"github.com/yourname/cyrene-ai/pkg/logger"
|
||||
"sync"
|
||||
|
||||
"github.com/yourname/cyrene-ai/ai-core/internal/bus"
|
||||
"github.com/yourname/cyrene-ai/ai-core/internal/llm"
|
||||
"github.com/yourname/cyrene-ai/ai-core/internal/model"
|
||||
)
|
||||
@@ -17,6 +18,7 @@ type Manager struct {
|
||||
mu sync.RWMutex
|
||||
providers map[model.SubSessionType]Provider
|
||||
llmClient LLMClient
|
||||
eventBus bus.Bus
|
||||
}
|
||||
|
||||
// NewManager 创建子会话管理器
|
||||
@@ -27,12 +29,24 @@ func NewManager(llmClient LLMClient) *Manager {
|
||||
}
|
||||
}
|
||||
|
||||
// SetBus sets the event bus (optional, for Phase 1).
|
||||
func (m *Manager) SetBus(b bus.Bus) {
|
||||
m.eventBus = b
|
||||
}
|
||||
|
||||
func (m *Manager) getBus() bus.Bus {
|
||||
if m.eventBus == nil {
|
||||
return &bus.NopBus{}
|
||||
}
|
||||
return m.eventBus
|
||||
}
|
||||
|
||||
// Register 注册子会话提供者
|
||||
func (m *Manager) Register(provider Provider) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.providers[provider.Type()] = provider
|
||||
log.Printf("[subsession] 注册子会话提供者: %s (优先级=%d, 超时=%v)", provider.Type(), provider.Priority(), provider.Timeout())
|
||||
logger.Printf("[subsession] 注册子会话提供者: %s (优先级=%d, 超时=%v)", provider.Type(), provider.Priority(), provider.Timeout())
|
||||
}
|
||||
|
||||
// RegisterWithOverride 注册或覆盖子会话提供者
|
||||
@@ -40,7 +54,7 @@ func (m *Manager) RegisterWithOverride(provider Provider) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.providers[provider.Type()] = provider
|
||||
log.Printf("[subsession] 注册(覆盖)子会话提供者: %s (优先级=%d, 超时=%v)", provider.Type(), provider.Priority(), provider.Timeout())
|
||||
logger.Printf("[subsession] 注册(覆盖)子会话提供者: %s (优先级=%d, 超时=%v)", provider.Type(), provider.Priority(), provider.Timeout())
|
||||
}
|
||||
|
||||
// GetProvider 获取指定类型的 Provider
|
||||
@@ -82,7 +96,7 @@ func (m *Manager) Dispatch(
|
||||
|
||||
for _, provider := range providers {
|
||||
if !provider.CanHandle(ctx, intent, userMessage) {
|
||||
log.Printf("[subsession] 跳过子会话 %s: CanHandle 返回 false", provider.Type())
|
||||
logger.Printf("[subsession] 跳过子会话 %s: CanHandle 返回 false", provider.Type())
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -91,11 +105,16 @@ func (m *Manager) Dispatch(
|
||||
defer wg.Done()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("[subsession] dispatch goroutine panic 恢复 (type=%s): %v", p.Type(), r)
|
||||
logger.Printf("[subsession] dispatch goroutine panic 恢复 (type=%s): %v", p.Type(), r)
|
||||
}
|
||||
}()
|
||||
|
||||
result := model.SubSessionResult{Type: p.Type()}
|
||||
m.getBus().Publish(bus.BusEvent{
|
||||
Type: bus.EventSubSessionStarted,
|
||||
Payload: bus.SubSessionPayload{SubType: p.Type(), Status: "started"},
|
||||
})
|
||||
|
||||
|
||||
// 创建带超时的 context
|
||||
subCtx, cancel := context.WithTimeout(ctx, p.Timeout())
|
||||
@@ -105,18 +124,18 @@ func (m *Manager) Dispatch(
|
||||
llmMessages, err := p.CreateContext(subCtx, params)
|
||||
if err != nil {
|
||||
result.Error = fmt.Sprintf("创建上下文失败: %v", err)
|
||||
log.Printf("[subsession] %s 创建上下文失败: %v", p.Type(), err)
|
||||
logger.Printf("[subsession] %s 创建上下文失败: %v", p.Type(), err)
|
||||
resultCh <- result
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[subsession] %s 开始执行 (上下文 %d 条消息)", p.Type(), len(llmMessages))
|
||||
logger.Printf("[subsession] %s 开始执行 (上下文 %d 条消息)", p.Type(), len(llmMessages))
|
||||
|
||||
// 执行子会话
|
||||
subResult, execErr := p.Execute(subCtx, llmMessages)
|
||||
if execErr != nil {
|
||||
result.Error = fmt.Sprintf("执行失败: %v", execErr)
|
||||
log.Printf("[subsession] %s 执行失败: %v", p.Type(), execErr)
|
||||
logger.Printf("[subsession] %s 执行失败: %v", p.Type(), execErr)
|
||||
resultCh <- result
|
||||
return
|
||||
}
|
||||
@@ -125,15 +144,20 @@ func (m *Manager) Dispatch(
|
||||
select {
|
||||
case <-subCtx.Done():
|
||||
result.Error = "子会话超时"
|
||||
log.Printf("[subsession] %s 超时 (limit=%v)", p.Type(), p.Timeout())
|
||||
logger.Printf("[subsession] %s 超时 (limit=%v)", p.Type(), p.Timeout())
|
||||
default:
|
||||
if subResult != nil {
|
||||
result = *subResult
|
||||
result.Type = p.Type()
|
||||
log.Printf("[subsession] %s 完成: 摘要=%s", p.Type(), truncate(result.Summary, 50))
|
||||
logger.Printf("[subsession] %s 完成: 摘要=%s", p.Type(), truncate(result.Summary, 50))
|
||||
}
|
||||
}
|
||||
|
||||
m.getBus().Publish(bus.BusEvent{
|
||||
Type: bus.EventSubSessionCompleted,
|
||||
Payload: bus.SubSessionPayload{SubType: p.Type(), Status: resultSummaryStatus(result), Summary: result.Summary, Details: result.Details},
|
||||
})
|
||||
|
||||
resultCh <- result
|
||||
}(provider)
|
||||
}
|
||||
@@ -142,7 +166,7 @@ func (m *Manager) Dispatch(
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("[subsession] wait goroutine panic 恢复: %v", r)
|
||||
logger.Printf("[subsession] wait goroutine panic 恢复: %v", r)
|
||||
}
|
||||
}()
|
||||
wg.Wait()
|
||||
@@ -159,6 +183,14 @@ func generateID() string {
|
||||
return fmt.Sprintf("sub-%x", b)
|
||||
}
|
||||
|
||||
// resultSummaryStatus returns "completed" or "failed" for bus events.
|
||||
func resultSummaryStatus(r model.SubSessionResult) string {
|
||||
if r.Error != "" {
|
||||
return "failed"
|
||||
}
|
||||
return "completed"
|
||||
}
|
||||
|
||||
// truncate 截断字符串
|
||||
func truncate(s string, maxLen int) string {
|
||||
runes := []rune(s)
|
||||
|
||||
@@ -3,7 +3,7 @@ package subsession
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"github.com/yourname/cyrene-ai/pkg/logger"
|
||||
"time"
|
||||
|
||||
"github.com/yourname/cyrene-ai/ai-core/internal/memory"
|
||||
@@ -88,14 +88,14 @@ func (p *MemoryProvider) Execute(ctx context.Context, subCtx []model.LLMMessage)
|
||||
}
|
||||
|
||||
if p.retriever == nil {
|
||||
log.Printf("[memory-subsession] 记忆检索器未初始化")
|
||||
logger.Printf("[memory-subsession] 记忆检索器未初始化")
|
||||
result.Summary = "(记忆系统未就绪)"
|
||||
return result, nil
|
||||
}
|
||||
|
||||
memories, err := p.retriever.Retrieve(ctx, userID, userMessage)
|
||||
if err != nil {
|
||||
log.Printf("[memory-subsession] 记忆检索失败: %v", err)
|
||||
logger.Printf("[memory-subsession] 记忆检索失败: %v", err)
|
||||
result.Error = fmt.Sprintf("检索失败: %v", err)
|
||||
result.Summary = "(记忆检索失败,但不影响对话)"
|
||||
return result, nil
|
||||
@@ -138,6 +138,6 @@ func (p *MemoryProvider) Execute(ctx context.Context, subCtx []model.LLMMessage)
|
||||
}
|
||||
|
||||
result.Memories = snippets
|
||||
log.Printf("[memory-subsession] 完成: %s", result.Summary)
|
||||
logger.Printf("[memory-subsession] 完成: %s", result.Summary)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package subsession
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"github.com/yourname/cyrene-ai/pkg/logger"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -64,7 +64,7 @@ func (p *ReviewProvider) Execute(_ context.Context, subCtx []model.LLMMessage) (
|
||||
|
||||
reviewMessages := parseReviewText(text)
|
||||
|
||||
log.Printf("[review-provider] 审查完成: 输入 %d 字符 → %d 条消息",
|
||||
logger.Printf("[review-provider] 审查完成: 输入 %d 字符 → %d 条消息",
|
||||
len([]rune(text)), len(reviewMessages))
|
||||
|
||||
// 构建摘要
|
||||
|
||||
Reference in New Issue
Block a user