feat: 人格文档双层架构 — persona.md全量注入system prompt + RAG黑名单过滤

- PersonaConfig 新增 NarrativePersona 字段,在 system prompt 的「你的身份」和「你的性格」之间插入「## 你的叙事人格」
- Loader.NewLoader 自动读取同目录 persona.md 注入所有 config;Reload 同步热更新
- RAG IngestDirectory 跳过 persona.md 和 _index.md,避免重复检索浪费token
- 新增 docs/dev-plan/12-next-phase-optimization.md 下一阶段优化大纲

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-06-22 20:25:45 +08:00
parent 6ef9e082a6
commit dbd89f39ab
3 changed files with 280 additions and 1 deletions
+9 -1
View File
@@ -17,6 +17,11 @@ type PersonaConfig struct {
ThinkingGuidelines ThinkingGuidelines `yaml:"thinking_guidelines"`
MemoryGuidelines MemoryGuidelines `yaml:"memory_guidelines"`
ReflectionGuidelines ReflectionGuidelines `yaml:"reflection_guidelines"`
// NarrativePersona is the full text of persona.md, injected verbatim
// into the system prompt as the narrative personality layer.
// Not parsed from YAML — loaded from persona.md alongside the YAML config.
NarrativePersona string `yaml:"-"`
}
// BuildSystemPrompt 构建系统Prompt (向后兼容,不含心情)
@@ -43,8 +48,11 @@ func (pc *PersonaConfig) BuildSystemPromptWithMood(userName string, affectionLev
## 你的身份
%s
## 你的叙事人格
%s
## 你的性格
`, pc.Identity.TrueName, pc.Identity.Essence)
`, pc.Identity.TrueName, pc.Identity.Essence, pc.NarrativePersona)
for _, trait := range pc.Personality.CoreTraits {
prompt += fmt.Sprintf("- %s: %s\n", trait.Name, trait.Description)
@@ -3,6 +3,7 @@ package persona
import (
"fmt"
"os"
"path/filepath"
"sync"
"gopkg.in/yaml.v3"
@@ -54,6 +55,17 @@ func NewLoader(personaDir string) (*Loader, error) {
return nil, fmt.Errorf("未找到任何人格配置文件")
}
// Load narrative persona from persona.md (same directory as YAML configs).
// This is the full-text personality narrative injected verbatim into the system prompt.
personaMDPath := filepath.Join(personaDir, "persona.md")
if data, err := os.ReadFile(personaMDPath); err == nil {
// Attach to every loaded persona config.
narrative := string(data)
for _, cfg := range l.configs {
cfg.NarrativePersona = narrative
}
}
return l, nil
}
@@ -82,6 +94,12 @@ func (l *Loader) Reload(name string, path string) error {
}
l.mu.Lock()
// Re-read persona.md to keep narrative persona in sync.
personaDir := filepath.Dir(path)
personaMDPath := filepath.Join(personaDir, "persona.md")
if data, err := os.ReadFile(personaMDPath); err == nil {
cfg.NarrativePersona = string(data)
}
l.configs[name] = &cfg
l.mu.Unlock()
+253
View File
@@ -0,0 +1,253 @@
# 12 — 下一阶段优化大纲
> **日期**2026-06-22
> **来源**:对照 Cyrene 人格、记忆及功能拓展架构设计文档,逐项比对现状后梳理
> **状态**:大纲,待拆分为详细实施计划
---
## 目录
1. [P0 — 自主思考分层](#1-p0--自主思考分层)
2. [P1 — LLM 可自主操作提醒与日程](#2-p1--llm-可自主操作提醒与日程)
3. [P2 — 对话后处理补全](#3-p2--对话后处理补全)
4. [P3 — 群聊并发(协会议话)](#4-p3--群聊并协会议话)
5. [P4 — 多平台前缀统一抽象](#5-p4--多平台前缀统一抽象)
6. [P5 — 工具子会话增强](#6-p5--工具子会话增强)
7. [实施顺序建议](#7-实施顺序建议)
---
## 1. P0 — 自主思考分层
### 现状
所有后台思考(periodic / silence / post_chat / startup)共用同一个 LLM 模型和同一套 system prompt。高频短思考(如每分钟检查状态)和低频深度反思(如每 5 分钟整理记忆)没有区分,浪费 token。
### 目标
```
┌─────────────┐ 唤醒 ┌─────────────┐
│ 简单思考 │ ──────────────► │ 复杂思考 │
│ (下位模型) │ │ (思考模型) │
│ 高频/轻量 │ │ 低频/深度 │
│ 例: 每分钟 │ │ 例: 每5分钟 │
└──────┬───────┘ └──────┬───────┘
│ 可调用工具 │ 可调用工具
│ 可主动消息 │ 可主动消息
│ 可追加继续 │ 可追加继续
▼ ▼
共享 ProactiveGuard 频率控制
```
### 要点
- **模型配置**:简单思考用 fast/flash 模型(temperature 0.3),复杂思考用 thinking 模型(temperature 0.7
- **周期**:简单思考 30-60s / 复杂思考 300s(可配置)
- **唤醒**:简单思考可判定"需要深思"→ 触发一次复杂思考
- **追加**:两种思考都允许 `continue` 信号追加(当前只有 post_chat 可追加)
- **后处理**:每次复杂思考结束后触发记忆整理
### 涉及文件
| 文件 | 改动 |
|------|------|
| `background/thinker.go` | Thinker 拆分为 SimpleThinker + DeepThinker 或增加双模式循环 |
| `config/loader.go``models.json` | 新增 `simple_think` / `deep_think` 路由 |
| `background/thinker.go``buildThinkingSystemPrompt` | 简单/复杂两套 prompt |
---
## 2. P1 — LLM 可自主操作提醒与日程
### 现状
Gateway 有 Reminder CRUD API,但 LLM 无法调用——没有注册 `reminder_create` / `reminder_update` / `schedule_query` 工具。昔涟不能在思考中说"明天早上 8 点提醒开拓者开会",因为这句话无法转化为实际的定时提醒。
### 目标
在 AI-Core 的 ToolRegistry 中新增 3-4 个提醒/日程工具,LLM 可在对话中或自主思考中调用。
### 要点
- **工具列表**
- `reminder_create(time, content, repeat?)` → 创建提醒
- `reminder_list()` → 列出当前用户的提醒
- `reminder_delete(id)` → 删除提醒
- `schedule_query(date)` → 查询某一天的日程
- **实现方式**HTTP 调用 Gateway `/api/v1/reminders` API(已有后端 CRUD
- **自主思考可调用**:加入 `AutonomousToolPolicy.AllowedTools` 白名单
- **防止阻塞**:配置 `THINK_SCHEDULE_WINDOW` 环境变量,在提醒时间窗口内确保简单思考被唤醒
### 涉及文件
| 文件 | 改动 |
|------|------|
| `ai-core/internal/tools/reminder_tool.go` | **新建** — 3-4 个 reminders 工具 |
| `ai-core/cmd/main.go` | 注册提醒工具到 ToolRegistry |
| `background/thinker.go``AutonomousToolPolicy` | 允许提醒工具在自主思考中调用 |
---
## 3. P2 — 对话后处理补全
### 3.1 自主思考后触发记忆整理
**现状**:记忆提取(`ExtractMemoriesOnly`)只在 `platform_silent` 和正常对话后触发。自主思考(periodic/silence/startup)结束后不触发。
**目标**:复杂思考结束后自动调用一次记忆整理。
**涉及文件**`background/thinker.go``performThink` 的结尾逻辑
### 3.2 对话后思考可追加
**现状**`post_chat` 思考支持追加(`allowContinuation` + `triggerReason == "post_chat"`),但 `periodic` / `silence` 不支持。
**目标**:所有思考类型都支持 `continue` 信号追加,通过 `MaxContinuationRounds` 限制防止死循环。
**涉及文件**`background/thinker.go` → 思考循环的 `allowContinuation` 条件
---
## 4. P3 — 群聊并发(协会议话)
### 现状
一个 session 的消息处理是严格线性的。当多个 QQ 用户同时在群里 @ 昔涟时:
- 消息按到达顺序排队
- 每条的 LLM 合成延迟 3-4s
- 第 3 条消息要等前面 2 条处理完才开始回复(6-8s 延迟)
### 目标
```
主会话 (session_id=platform_qq_<groupID>)
├─ 消息A → 主会话处理 (LLM 合成中...)
├─ 消息B (主会话繁忙) → 协会议话 B (独立 LLM 合成)
│ └─ 完成 → 结果写入主会话上下文
└─ 消息C (主会话繁忙) → 协会议话 C (独立 LLM 合成)
└─ 完成 → 结果写入主会话上下文
```
### 要点
- **并发检测**:收到新消息时,检查主会话是否正在处理(`isProcessing` 标记)
- **协会议话**:独立 goroutine + 独立 LLM 调用,共享主会话的对话历史
- **结果合并**:协会议话完成后,回复通过 platform-bridge 发送,上下文摘要写回主会话
- **数量限制**:最多 3 个协会议话并行,超出则排队
- **身份一致性**:每个协会议话携带相同的 persona config + 群聊上下文
### 涉及文件
| 文件 | 改动 |
|------|------|
| `ai-core/orchestrator/orchestrator.go` | ProcessInput 增加并发检测 + 协会议话创建 |
| `ai-core/orchestrator/concurrent_handler.go` | **新建** — 协会议话管理器 |
| `platform-bridge/cmd/main.go` | 可能需要支持协会议话的回复路由 |
---
## 5. P4 — 多平台前缀统一抽象
### 现状
QQ 平台标识(`【QQ群聊:xxx】``【QQ私聊:xxx】`)硬编码在 `thinker.go``buildThinkingUserPrompt` 里,`extractProactiveMessage` 里的 `qqTargetRe` 也只匹配 QQ。如果要接入 Telegram / Discord / 企业微信,需要在多个位置添加硬编码。
### 目标
将"平台前缀"抽象为一个统一的 `PlatformRouteFormat` 接口,每个 adapter 注册时声明自己的格式。
### 要点
- `PlatformRouteFormat` 接口:
- `ProactiveMarker(chatType, channelID, atUserID string) string` — 主动消息标记格式
- `ChannelPrefix(channelID string) string` — 消息来源前缀(如 `【QQ群聊:xxx】`
- 注册在 `PlatformAdapter` 接口扩展中
- thinker prompt 和 extractProactiveMessage 从 router 获取当前已注册的格式列表
- 后向兼容:QQ 当前行为不变
### 涉及文件
| 文件 | 改动 |
|------|------|
| `platform-bridge/bridge/adapter.go` | PlatformAdapter 接口扩展 |
| `platform-bridge/bridge/router.go` | GetRouteFormats() 方法 |
| `platform-bridge/handler/bridge_handler.go` | API 端点暴露格式信息 |
| `ai-core/background/thinker.go` | 从 bridge 获取格式替代硬编码 |
| `ai-core/cmd/main.go` | 启动时拉取格式配置 |
---
## 6. P5 — 工具子会话增强
### 6.1 子会话返回时间戳
**现状**:子会话结果(SubSessionResult)没有记录创建时间和结束时间。
**目标**:每个子会话结果增加 `CreatedAt` / `FinishedAt` 字段。
**涉及文件**`model/sub_session.go` + 各 provider 的 `Execute()` 方法
### 6.2 工具调用不阻塞主会话
**现状**`Synthesizer.ChatWithTools` 的 LLM 工具调用循环(最多 5 轮)在主 goroutine 中执行,长工具调用(如 `web_search` + `web_fetch` 链)会阻塞流式输出。
**目标**:工具调用结果通过 channel 异步返回,Synthesizer 先输出等待提示(如"让我查一下..."),收到结果后再继续流式生成。
**涉及文件**`orchestrator/synthesizer.go``ChatWithTools` 的调用模型改为 publish/subscribe
---
## 7. 实施顺序建议
```
Phase A (本节可做) ─────────────────────────────
P2.2 对话后思考可追加 ~10 行改动
P2.1 自主思考后触发记忆整理 ~5 行改动
P5.1 子会话返回时间戳 ~20 行改动
Phase B (需要跨服务协调) ───────────────────────
P1 LLM 可自主操作提醒与日程 新建 1 文件 + 注册工具
P0 自主思考分层 重构 thinker 核心循环
Phase C (架构级改动) ───────────────────────────
P4 多平台前缀统一抽象 跨 ai-core + platform-bridge
P3 群聊并发(协会议话) 跨 ai-core + platform-bridge
Phase D (后续优化) ─────────────────────────────
P5.2 工具调用不阻塞主会话 改造 synthesizer 调用模型
```
---
## 附:与架构设计文档的对照清单
| # | 设计要求 | 状态 | 对应章节 |
|---|---------|------|---------|
| 1 | 基础出人格库 | ✅ | — |
| 2 | 故事背景知识库 | ✅ | — |
| 3 | 多优先级自主增删改记忆 | ⚠️ 缺自主删改 | 待定 |
| 4 | 用户手动增删改知识库 | ✅ | — |
| 5 | IoT 功能调用知识库 | ✅ | — |
| 6 | 插件功能调用知识库 | ✅ | — |
| 7 | 定时任务模块 | ❌ | P1 |
| 8 | 定时任务自主增删改 | ❌ | P1 |
| 9 | 日程自主增删改 | ❌ | P1 |
| 10 | 简单思考(高频下位模型) | ❌ | P0 |
| 11 | 复杂思考(低频思考模型) | ⚠️ 有但未分层 | P0 |
| 12 | 简单思考可唤醒复杂思考 | ❌ | P0 |
| 13 | 两种思考均可追加 | ⚠️ 仅 post_chat | P2.2 |
| 14 | 两种思考均可调用工具 | ✅ | — |
| 15 | 主动通讯(客户端+平台) | ✅ | — |
| 16 | 思考后触发记忆整理 | ❌ | P2.1 |
| 17 | 回复一句一句发 | ✅ | — |
| 18 | 工具调用不阻塞主会话 | ⚠️ 部分阻塞 | P5.2 |
| 19 | 工具子会话返回时间戳 | ❌ | P5.1 |
| 20 | 群聊并发协会议话 | ❌ | P3 |
| 21 | 多平台消息前缀 | ⚠️ QQ 硬编码 | P4 |
| 22 | 多平台发送方式 | ✅ | — |
> 共 22 项设计要求:✅ 已完成 9 项,⚠️ 部分完成 5 项,❌ 未实现 8 项。