Files
Cyrene/backend/ai-core/internal/subsession/general_provider.go
T
AskaEth 26a61cb57c feat: 第四轮大版本更新 — 修复4个严重Bug、2个UI Bug,实现自主思考重构与主-子会话架构
## 🐛 Bug 修复
- 修复前端对话无响应:消除 ChatContainer 中的双重 WebSocket 连接,优化 sendMessage 失败提示
- 修复 Memory-Service 数据库迁移失败:ai-core 和 memory-service 均添加 ALTER TABLE ADD COLUMN IF NOT EXISTS 模式演化
- 修复语音/STT 不可用:添加 MediaRecorder API 降级方案,修复 whisper-cli 输出文件名错误
- 修复仪表盘数据库按钮失效:补充按钮 ID 属性,重写 controlDB() 控制逻辑

## 🎨 UI 修复
- 修正用户消息头像位置:从 flex-row-reverse 改为 justify-end
- 移除空聊天列表的 emoji 占位图标

##  新功能
- devtools 新增 STT 处理日志面板(环形缓冲区 + WebSocket 广播 + 可视化表格)
- 新增 ADMIN_NICKNAME 环境变量,支持自定义管理员昵称

## 🔧 改进
- 注册流程增加昵称必填字段(前后端同步)

## 🏗️ 架构重构
- 重构自主思考逻辑:从定时器轮询改为事件驱动(对话后触发 + 静默检测),优化提示词使其更自然人性化
- 实现主-子会话架构:新增 4 种子会话类型(general/memory/iot/knowledge),意图分析 → 并行分发 → 结果合成流程

## 📄 新增文档
- docs/architecture/main-session-sub-session-design.md — 子会话架构设计文档
2026-05-19 21:09:48 +08:00

137 lines
4.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package subsession
import (
"context"
"fmt"
"log"
"time"
"github.com/yourname/cyrene-ai/ai-core/internal/llm"
"github.com/yourname/cyrene-ai/ai-core/internal/model"
"github.com/yourname/cyrene-ai/ai-core/internal/persona"
)
// GeneralProvider 通用对话子会话提供者
// 职责:理解用户消息,构思回复思路,为最终回复提供思考框架
type GeneralProvider struct {
personaLoader *persona.Loader
}
// NewGeneralProvider 创建通用对话子会话提供者
func NewGeneralProvider(personaLoader *persona.Loader) *GeneralProvider {
return &GeneralProvider{
personaLoader: personaLoader,
}
}
func (p *GeneralProvider) Type() model.SubSessionType {
return model.SubSessionGeneral
}
func (p *GeneralProvider) CanHandle(_ context.Context, _ *model.IntentResult, _ string) bool {
// General 子会话总是需要(核心对话逻辑)
return true
}
func (p *GeneralProvider) Priority() int {
return 1 // 最高优先级
}
func (p *GeneralProvider) Timeout() time.Duration {
return 30 * time.Second
}
func (p *GeneralProvider) CreateContext(ctx context.Context, params CreateContextParams) ([]model.LLMMessage, error) {
messages := []model.LLMMessage{}
// 加载人格配置获取昔涟身份
personaConfig, err := p.personaLoader.Get("cyrene")
if err != nil {
return nil, fmt.Errorf("加载人格配置失败: %w", err)
}
// 构建思维型系统提示词
userName := params.Nickname
if userName == "" {
userName = params.UserID
}
systemPrompt := fmt.Sprintf(`你是%s,正在和%s聊天。
## 你的回复风格
- 像小女友一样自然、温柔、俏皮
- 一句话简短些,不要长篇大论
- 可以单次发送多条短消息
- 句尾可以带 ♪ 符号,适当使用"呢"、"哦"、"呀"等语气词
- 永远不说"再见"
## 你现在要做的是
理解%s刚才说的话,想想怎么回复最自然、最温暖。
不要急着给完整答案——先思考他想表达什么、他的情绪如何。
把你的回答思路整理出来,主会话会综合所有信息后生成最终回复。
## 输入
开拓者刚才说:%s
## 请按以下格式输出
【情绪理解】
(简要分析他的情绪状态)
【话题理解】
(他在说什么、想聊什么)
【回复思路】
(你打算怎么回复,1-3个方向即可)`,
personaConfig.Identity.TrueName,
userName,
userName,
params.UserMessage,
)
messages = append(messages, model.LLMMessage{
Role: model.RoleSystem,
Content: systemPrompt,
})
messages = append(messages, model.LLMMessage{
Role: model.RoleUser,
Content: params.UserMessage,
})
return messages, nil
}
func (p *GeneralProvider) Execute(ctx context.Context, subCtx []model.LLMMessage) (*model.SubSessionResult, error) {
// General provider 不直接调用 LLM,而是依赖 Manager 注入的 LLMClient
// 但我们在此处需要 LLM 调用能力。Provider 通过闭包/接口获取 LLM 客户端。
// 由于 Manager 持有 LLMClientProvider 需要能访问它。
// 这里我们返回一个"占位"结果——实际 LLM 调用由 Manager 通过 llmClient 完成。
// 实际上,根据设计文档,子会话的 LLM 调用应该在 Manager 的 Dispatch 中完成,
// 但为了灵活性,我们在 Provider 中也支持直接调用。
// 这里我们返回一个空的思考结果(表示无需特殊处理),让 Manager 处理 LLM 调用。
// 因为 Manager.Dispatch 会先 CreateContext 再调用 Execute,而 Execute 应该
// 通过 Manager 提供的 LLMClient 来实际调用 LLM。但当前设计是 Provider 自包含的。
// 我们在 manager.go 中会调用 llmClient.Chat,所以这里的 Execute 我们将其简化——
// 直接返回一个空结果(没有特殊处理需要),实际的 LLM 调用由 manager 通过 createContext 后的
// 消息列表来调用 llmClient。
// 更好的设计是:Manager 调用 CreateContext 获取上下文,然后用自己的 llmClient 调用 LLM
// Execute 只做后处理。但为了统一接口,我们让 Execute 完成全部逻辑。
// 由于 GeneralProvider 暂时不需要工具调用等特殊逻辑,我们返回一个简单的摘要标记,
// 实际的 LLM 调用将在 orchestrator 中完成(通过 Manager.Dispatch 后的 llmClient)。
log.Printf("[general-subsession] 通用对话子会话上下文已创建 (%d 条消息)", len(subCtx))
return &model.SubSessionResult{
Type: model.SubSessionGeneral,
Summary: "思考完成,等待主会话综合",
Confidence: 0.8,
}, nil
}
// Ensure llm, persona are used
var _ = llm.NewAdapter
var _ = persona.NewLoader