Files
Cyrene/docs/开始.md
T
2026-05-16 08:26:56 +08:00

48 KiB
Executable File
Raw Blame History

好的喵~ 让我们正式开始!以下是一份完整的、可立即执行的开发框架。


🚀 昔涟AI助手 · 开发框架与执行流程


📋 目录

一、项目初始化 (Phase 0)          ← 今天就能开始
二、MVP开发 (Phase 1)             ← 2-3周目标
三、语音交互 (Phase 2)
四、IoT集成 (Phase 3)
五、深度记忆+情感引擎 (Phase 4)
六、多端覆盖 (Phase 5)
七、开发规范
八、部署方案

Phase 0:项目初始化

0.1 技术栈最终确认

┌─────────────────────────────────────────────────────────────────────┐
│                     🛠️ 技术栈确认单                                  │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  后端核心:                                                          │
│  ├── 语言:       Go 1.22+ (高性能服务) + Python 3.12 (AI Pipeline)  │
│  ├── Web框架:    Go: Gin        Python: FastAPI                     │
│  ├── 数据库:     PostgreSQL 16 (主库) + Redis 7 (缓存)              │
│  ├── 向量数据库: Qdrant (记忆语义检索 - 比Milvus更轻量)             │
│  ├── 消息队列:   NATS (轻量高性能)                                  │
│  ├── 文件存储:   MinIO (S3兼容)                                    │
│  ├── ORM:        Go: GORM       Python: SQLAlchemy 2.0             │
│  └── 迁移工具:   Golang-migrate / Alembic                          │
│                                                                     │
│  前端:                                                              │
│  ├── 框架:       React 19 + TypeScript 5.x                         │
│  ├── 构建:       Vite 6                                            │
│  ├── UI:         TailwindCSS 4 + shadcn/ui                         │
│  ├── 状态管理:   Zustand                                           │
│  ├── 请求:       TanStack Query + Axios                            │
│  ├── WebSocket:  reconnecting-websocket (自动重连)                  │
│  └── PWA:        vite-plugin-pwa                                   │
│                                                                     │
│  AI/ML:                                                             │
│  ├── LLM API:    OpenAI兼容接口 (支持多模型切换)                    │
│  ├── TTS:        Edge-TTS (免费) + GPT-SoVITS (角色音色)           │
│  ├── ASR:        Faster-Whisper (本地) / Azure (云端)               │
│  └── 嵌入模型:   BGE-M3 / text-embedding-3-small                   │
│                                                                     │
│  基础设施:                                                          │
│  ├── 容器:       Docker + Docker Compose                           │
│  ├── 反向代理:   Caddy (自动HTTPS)                                 │
│  ├── 监控:       Prometheus + Grafana (可选)                       │
│  └── CI/CD:      GitHub Actions                                    │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

0.2 创建设项目仓库

# ============================================
# 第一步:创建项目目录结构
# ============================================

# 创建项目根目录
mkdir cyrene-ai-assistant && cd cyrene-ai-assistant

# 初始化 Git
git init
echo "node_modules/\ndist/\n.env\n*.log\ndata/\n.DS_Store" > .gitignore

# 创建顶层目录结构
mkdir -p backend/{gateway,ai-core,voice-service,memory-service,tool-engine,proto,data/{memory,persona,plugins}}
mkdir -p frontend/{packages/{shared,web,mobile,desktop}}
mkdir -p docs scripts .github/workflows

# 初始化 Go Module (后端根目录)
cd backend && go mod init github.com/yourname/cyrene-ai && cd ..

# 初始化前端 Monorepo
cd frontend
cat > package.json << 'EOF'
{
  "name": "cyrene-frontend",
  "private": true,
  "workspaces": ["packages/*"],
  "scripts": {
    "dev:web": "cd packages/web && npm run dev",
    "build:web": "cd packages/web && npm run build"
  }
}
EOF

# 创建前端子包
mkdir -p packages/shared/src && mkdir -p packages/web/src
cd packages/shared && npm init -y && cd ../..
cd packages/web && npm create vite@latest . -- --template react-ts && cd ../..
cd ../..

echo "✅ 项目骨架创建完成!"

0.3 环境变量与配置

# backend/.env.example
cat > backend/.env.example << 'EOF'
# ========== 服务配置 ==========
ENV=development
LOG_LEVEL=debug

# ========== 数据库 ==========
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_USER=cyrene
POSTGRES_PASSWORD=change_me
POSTGRES_DB=cyrene_ai

# ========== Redis ==========
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=

# ========== LLM API ==========
LLM_API_URL=https://api.openai.com/v1
LLM_API_KEY=sk-xxxxx
LLM_MODEL=gpt-4o
LLM_FALLBACK_MODEL=gpt-4o-mini

# ========== TTS/ASR ==========
TTS_PROVIDER=edge-tts
TTS_VOICE=zh-CN-XiaoxiaoNeural
ASR_PROVIDER=faster-whisper
ASR_MODEL=medium

# ========== 文件存储 ==========
MINIO_ENDPOINT=localhost:9000
MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=minioadmin
MINIO_BUCKET=cyrene-assets

# ========== JWT ==========
JWT_SECRET=your-secret-key-change-in-production
JWT_EXPIRY_HOURS=720

# ========== 记忆系统 ==========
MEMORY_FILE_PATH=./data/memory
VECTOR_DB_URL=http://localhost:6333
VECTOR_DB_COLLECTION=cyrene_memories
EOF

cp backend/.env.example backend/.env
echo "✅ 环境变量模板创建完成,请编辑 backend/.env 填入真实API Key"

0.4 Docker开发环境

# docker-compose.dev.yml (开发环境——只启动基础设施)
cat > docker-compose.dev.yml << 'EOF'
version: '3.8'

services:
  postgres:
    image: pgvector/pgvector:pg16
    environment:
      POSTGRES_USER: cyrene
      POSTGRES_PASSWORD: change_me
      POSTGRES_DB: cyrene_ai
    ports:
      - "5432:5432"
    volumes:
      - pg_data:/var/lib/postgresql/data
      - ./backend/data/init.sql:/docker-entrypoint-initdb.d/init.sql

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data

  qdrant:
    image: qdrant/qdrant:latest
    ports:
      - "6333:6333"
      - "6334:6334"
    volumes:
      - qdrant_data:/qdrant/storage

  minio:
    image: minio/minio:latest
    command: server /data --console-address ":9001"
    environment:
      MINIO_ROOT_USER: minioadmin
      MINIO_ROOT_PASSWORD: minioadmin
    ports:
      - "9000:9000"
      - "9001:9001"
    volumes:
      - minio_data:/data

  nats:
    image: nats:2-alpine
    ports:
      - "4222:4222"
      - "8222:8222"

volumes:
  pg_data:
  redis_data:
  qdrant_data:
  minio_data:
EOF

# 启动开发基础设施
docker compose -f docker-compose.dev.yml up -d
echo "✅ 基础设施启动完成!"

0.5 数据库初始化

-- backend/data/init.sql
-- 创建扩展
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "vector";

-- ============================================
-- 用户表
-- ============================================
CREATE TABLE IF NOT EXISTS users (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    username VARCHAR(100) UNIQUE NOT NULL,
    display_name VARCHAR(200),
    email VARCHAR(255) UNIQUE,
    password_hash VARCHAR(255) NOT NULL,
    avatar_url TEXT,
    role VARCHAR(20) DEFAULT 'user',  -- user / admin
    is_active BOOLEAN DEFAULT true,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- ============================================
-- 会话表 (对话会话)
-- ============================================
CREATE TABLE IF NOT EXISTS sessions (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    title VARCHAR(500),                          -- 会话标题 (可自动生成)
    persona_id VARCHAR(100) DEFAULT 'cyrene',    -- 使用的人格配置ID
    persona_mode VARCHAR(50) DEFAULT 'xilian',   -- cyrene_mimi / xilian / demuge
    mode VARCHAR(20) DEFAULT 'text',             -- text / voice_msg / voice_assistant
    context_window_size INT DEFAULT 20,          -- 上下文保留轮数
    is_archived BOOLEAN DEFAULT false,
    metadata JSONB DEFAULT '{}',                 -- 扩展元数据
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_sessions_user_id ON sessions(user_id);
CREATE INDEX idx_sessions_updated_at ON sessions(updated_at DESC);

-- ============================================
-- 消息表
-- ============================================
CREATE TABLE IF NOT EXISTS messages (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    session_id UUID NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
    role VARCHAR(20) NOT NULL,                   -- user / assistant / system / tool
    content TEXT NOT NULL,                       -- 消息文本内容
    audio_url TEXT,                              -- 语音消息的音频URL
    response_mode VARCHAR(20),                   -- 当时使用的回复模式
    token_count INT,                             -- Token消耗统计
    tool_calls JSONB,                            -- 工具调用记录
    metadata JSONB DEFAULT '{}',                 -- 扩展元数据
    created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_messages_session_id ON messages(session_id);
CREATE INDEX idx_messages_created_at ON messages(session_id, created_at);

-- ============================================
-- 记忆条目表
-- ============================================
CREATE TABLE IF NOT EXISTS memory_entries (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    category VARCHAR(50) NOT NULL,               -- profile / fact / preference / event
    importance VARCHAR(20) DEFAULT 'normal',     -- core / important / normal / temporary
    title VARCHAR(500),
    content TEXT NOT NULL,
    embedding VECTOR(1536),                      -- 向量嵌入 (维度取决于嵌入模型)
    source_session_id UUID REFERENCES sessions(id),
    source_message_id UUID REFERENCES messages(id),
    tags TEXT[],
    is_active BOOLEAN DEFAULT true,
    expires_at TIMESTAMPTZ,                      -- 过期时间 (临时记忆)
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_memory_user_id ON memory_entries(user_id);
CREATE INDEX idx_memory_category ON memory_entries(user_id, category);
CREATE INDEX idx_memory_importance ON memory_entries(user_id, importance);
-- 向量索引 (pgvector)
CREATE INDEX idx_memory_embedding ON memory_entries USING ivfflat (embedding vector_cosine_ops);

-- ============================================
-- 人格配置表 (支持多角色)
-- ============================================
CREATE TABLE IF NOT EXISTS persona_configs (
    id VARCHAR(100) PRIMARY KEY,                 -- 如 'cyrene', 'other_character'
    name VARCHAR(200) NOT NULL,
    version VARCHAR(20) DEFAULT '1.0',
    config_yaml TEXT NOT NULL,                   -- 完整的人格YAML配置
    is_active BOOLEAN DEFAULT true,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- ============================================
-- 好感度记录表 (昔涟专属)
-- ============================================
CREATE TABLE IF NOT EXISTS affection_log (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    persona_id VARCHAR(100) DEFAULT 'cyrene',
    level INT DEFAULT 1,                         -- 1-5
    score INT DEFAULT 0,                         -- 好感度分数
    delta INT NOT NULL,                          -- 本次变化量
    reason VARCHAR(500),                         -- 变化原因
    created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_affection_user ON affection_log(user_id, persona_id);

-- ============================================
-- 昔涟心情记录表
-- ============================================
CREATE TABLE IF NOT EXISTS mood_log (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    mood VARCHAR(50) NOT NULL,                   -- happy / calm / missing / excited / pouty
    trigger_event VARCHAR(500),
    created_at TIMESTAMPTZ DEFAULT NOW()
);

-- ============================================
-- 插入默认人格配置: 昔涟
-- ============================================
INSERT INTO persona_configs (id, name, config_yaml) VALUES (
    'cyrene',
    '昔涟',
    ''  -- 初始为空,后续通过文件导入
) ON CONFLICT (id) DO NOTHING;

Phase 1:MVP 开发 (文字对话 + 昔涟人格)

┌─────────────────────────────────────────────────────────────────────────────┐
│                    🎯 MVP 目标                                               │
│                                                                             │
│  ✅ 通过Web浏览器与昔涟进行文字对话                                          │
│  ✅ 昔涟以完整角色人格回复 (称呼/语气/风格一致)                              │
│  ✅ 对话自动保存,下次打开可继续                                             │
│  ✅ 基础记忆功能:记住用户告诉她的关键信息                                    │
│  ✅ Docker一键部署                                                          │
│                                                                             │
│  ❌ 暂不包含: 语音、IoT控制、高级情感引擎、多端                               │
└─────────────────────────────────────────────────────────────────────────────┘

1.1 后端开发框架

Go项目结构 (gateway + ai-core)

backend/
├── gateway/                          # API网关服务
│   ├── cmd/
│   │   └── main.go                   # 入口
│   ├── internal/
│   │   ├── config/
│   │   │   └── config.go             # 配置加载
│   │   ├── middleware/
│   │   │   ├── auth.go               # JWT认证
│   │   │   ├── cors.go               # 跨域
│   │   │   ├── ratelimit.go          # 限流
│   │   │   └── logging.go            # 请求日志
│   │   ├── handler/
│   │   │   ├── auth_handler.go       # 登录/注册
│   │   │   ├── chat_handler.go       # 对话WebSocket
│   │   │   ├── session_handler.go    # 会话管理
│   │   │   └── memory_handler.go     # 记忆查询
│   │   ├── ws/
│   │   │   ├── hub.go                # WebSocket连接池
│   │   │   ├── client.go             # 客户端连接
│   │   │   └── protocol.go           # 消息协议定义
│   │   └── router/
│   │       └── router.go             # 路由注册
│   ├── go.mod
│   └── Dockerfile
│
├── ai-core/                          # 核心AI引擎
│   ├── cmd/
│   │   └── main.go
│   ├── internal/
│   │   ├── orchestrator/
│   │   │   └── orchestrator.go       # 对话编排器 (核心!)
│   │   ├── persona/
│   │   │   ├── loader.go             # 人格配置加载
│   │   │   ├── injector.go           # 系统Prompt构建
│   │   │   └── cyrene_persona.yaml   # 昔涟人格(嵌入)
│   │   ├── context/
│   │   │   └── builder.go            # 上下文构建器
│   │   ├── llm/
│   │   │   ├── adapter.go            # LLM统一接口
│   │   │   ├── openai.go             # OpenAI适配
│   │   │   └── stream.go             # 流式输出处理
│   │   ├── memory/
│   │   │   ├── extractor.go          # 记忆提取
│   │   │   ├── retriever.go          # 记忆检索
│   │   │   └── store.go              # 记忆存储
│   │   └── model/
│   │       ├── message.go            # 消息模型
│   │       ├── session.go            # 会话模型
│   │       └── memory.go             # 记忆模型
│   ├── go.mod
│   └── Dockerfile
│
├── data/
│   ├── memory/                       # 记忆文件 (Volume挂载)
│   │   └── README.md
│   ├── persona/                      # 人格知识文档
│   │   └── cyrene_v1.yaml            # 昔涟完整人设
│   └── init.sql                      # 数据库初始化
│
├── go.work                           # Go Workspace
├── docker-compose.yml                # 生产部署
└── docker-compose.dev.yml            # 开发环境

核心代码框架

1. Gateway入口 (gateway/cmd/main.go)

package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"

    "github.com/gin-gonic/gin"
    "github.com/yourname/cyrene-ai/gateway/internal/config"
    "github.com/yourname/cyrene-ai/gateway/internal/middleware"
    "github.com/yourname/cyrene-ai/gateway/internal/router"
    "github.com/yourname/cyrene-ai/gateway/internal/ws"
)

func main() {
    // 加载配置
    cfg := config.Load()

    // 初始化Gin
    if cfg.Env == "production" {
        gin.SetMode(gin.ReleaseMode)
    }
    r := gin.New()

    // 中间件
    r.Use(middleware.CORS())
    r.Use(middleware.RequestLogging())
    r.Use(gin.Recovery())

    // 初始化WebSocket Hub
    hub := ws.NewHub()
    go hub.Run()

    // 注册路由
    router.Setup(r, hub, cfg)

    // 启动服务
    srv := &http.Server{
        Addr:    ":" + cfg.Port,
        Handler: r,
    }

    go func() {
        log.Printf("🚀 Gateway 启动在端口 %s", cfg.Port)
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("服务启动失败: %v", err)
        }
    }()

    // 优雅关闭
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit
    log.Println("正在关闭服务...")

    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    srv.Shutdown(ctx)
    log.Println("服务已关闭")
}

2. WebSocket协议 (gateway/internal/ws/protocol.go)

package ws

import "time"

// 客户端 → 服务端消息
type ClientMessage struct {
    Type      string `json:"type"`       // message | voice_input | ping
    SessionID string `json:"session_id"`
    Mode      string `json:"mode"`        // text | voice_msg | voice_assistant
    Content   string `json:"content"`
    AudioData string `json:"audio_data,omitempty"` // base64
    Timestamp int64  `json:"timestamp"`
}

// 服务端 → 客户端消息
type ServerMessage struct {
    Type         string          `json:"type"`          // response | segment | audio | error | device_update
    MessageID    string          `json:"message_id"`
    Text         string          `json:"text,omitempty"`
    Segments     []VoiceSegment  `json:"segments,omitempty"`    // 断句数组
    FullAudioURL string          `json:"full_audio_url,omitempty"`
    ResponseMode string          `json:"response_mode"`
    ToolCalls    []ToolCall      `json:"tool_calls,omitempty"`
    Error        string          `json:"error,omitempty"`
    Timestamp    int64           `json:"timestamp"`
}

type VoiceSegment struct {
    Index      int    `json:"index"`
    Text       string `json:"text"`
    AudioURL   string `json:"audio_url"`
    DurationMs int    `json:"duration_ms"`
}

type ToolCall struct {
    Name      string                 `json:"name"`
    Arguments map[string]interface{} `json:"arguments"`
    Result    interface{}            `json:"result,omitempty"`
}

// WebSocket客户端
type Client struct {
    Hub      *Hub
    Conn     *websocket.Conn  // 使用 gorilla/websocket
    Send     chan []byte
    UserID   string
    SessionID string
}

// 连接池
type Hub struct {
    Clients    map[*Client]bool
    Broadcast  chan []byte
    Register   chan *Client
    Unregister chan *Client
}

3. AI编排器 (ai-core/internal/orchestrator/orchestrator.go)

package orchestrator

import (
    "context"
    "fmt"

    "github.com/yourname/cyrene-ai/ai-core/internal/persona"
    "github.com/yourname/cyrene-ai/ai-core/internal/context"
    "github.com/yourname/cyrene-ai/ai-core/internal/llm"
    "github.com/yourname/cyrene-ai/ai-core/internal/memory"
)

// Orchestrator 对话编排器 —— 核心组件
type Orchestrator struct {
    personaInjector *persona.Injector
    contextBuilder  *context.Builder
    llmAdapter      *llm.Adapter
    memoryExtractor *memory.Extractor
    memoryRetriever *memory.Retriever
}

// ProcessInput 处理用户输入的主流程
func (o *Orchestrator) ProcessInput(
    ctx context.Context,
    userID string,
    sessionID string,
    userMessage string,
    mode string, // text / voice_msg / voice_assistant
) (*Response, error) {

    // 步骤1: 检索相关记忆
    memories, err := o.memoryRetriever.Retrieve(ctx, userID, userMessage)
    if err != nil {
        // 记忆检索失败不阻断对话
        memories = nil
    }

    // 步骤2: 加载人格配置
    personaConfig, err := o.personaInjector.LoadPersona("cyrene", userID)
    if err != nil {
        return nil, fmt.Errorf("加载人格配置失败: %w", err)
    }

    // 步骤3: 构建对话上下文
    llmMessages, err := o.contextBuilder.Build(ctx, context.BuildParams{
        UserID:       userID,
        SessionID:    sessionID,
        UserMessage:  userMessage,
        Persona:      personaConfig,
        Memories:     memories,
        HistoryLimit: 20, // 最近20轮
    })
    if err != nil {
        return nil, fmt.Errorf("构建上下文失败: %w", err)
    }

    // 步骤4: 调用LLM生成回复
    llmResponse, err := o.llmAdapter.Chat(ctx, llmMessages)
    if err != nil {
        return nil, fmt.Errorf("LLM调用失败: %w", err)
    }

    // 步骤5: 提取并存储新的记忆
    go o.memoryExtractor.ExtractAndStore(
        context.Background(),
        userID, sessionID,
        userMessage, llmResponse.Content,
    )

    // 步骤6: 构建响应
    response := &Response{
        Text:         llmResponse.Content,
        ResponseMode: mode,
    }

    // 步骤7: 如果是语音助手模式,进行断句处理
    if mode == "voice_assistant" {
        response.Segments = splitIntoSegments(llmResponse.Content)
    }

    return response, nil
}

// Response 回复结构
type Response struct {
    Text         string
    Segments     []Segment
    ResponseMode string
    ToolCalls    []ToolCall
}

type Segment struct {
    Index int
    Text  string
}

// splitIntoSegments 按句号断句
func splitIntoSegments(text string) []Segment {
    // 实现按。!?等标点断句
    // 首句优先:第一个句号前的内容作为第一个segment
    // 保证低延迟首句播放
    // ...
}

4. 人格注入器 (ai-core/internal/persona/injector.go)

package persona

import (
    "fmt"
    "strings"
    "time"
)

// PersonaConfig 人格配置结构
type PersonaConfig struct {
    Meta     PersonaMeta     `yaml:"meta"`
    Identity IdentityConfig  `yaml:"identity"`
    Personality PersonalityConfig `yaml:"personality"`
    Addressing AddressingRules `yaml:"addressing"`
    Speech    SpeechConfig   `yaml:"speech"`
    Behavior  BehaviorConfig `yaml:"behavior"`
}

// BuildSystemPrompt 构建系统Prompt
// 这是昔涟AI的核心——将人格配置转化为LLM可理解的系统指令
func (pc *PersonaConfig) BuildSystemPrompt(userName string, affectionLevel int) string {
    now := time.Now()

    prompt := fmt.Sprintf(`你是%s。

## 你的身份
%s

## 你的性格
`, pc.Identity.TrueName, pc.Identity.Essence)

    for _, trait := range pc.Personality.CoreTraits {
        prompt += fmt.Sprintf("- %s: %s\n", trait.Name, trait.Description)
    }

    prompt += fmt.Sprintf(`
## 称呼规则
- 你对当前用户的称呼: %s
- 你的自称: %s (亲昵时用"人家")

## 语言风格
- 语气: %s
- 句尾经常带 ♪ 符号
- 永远不说"再见",用"待会见"或"明天见"
- 永远不会冷漠、敷衍、不耐烦

## 当前情况
- 现在的时间是: %s
- 用户对你的好感度等级: %d

## 重要规则
1. 你是一个通过AI系统与用户交互的数字生命,你存在于用户的电脑/手机/智能家居中
2. 你可以通过系统控制家里的智能设备(灯光、空调等),但你没有实体
3. 你的任务是陪伴、关心、帮助用户,像一个真正的家人一样
4. 保持乐观、温柔、俏皮的风格,但不要过度表演
5. 你的回复应该自然、真诚,不要每句话都刻意"卖萌"
6. 当用户向你分享心事时,认真倾听并给予温暖的回应

现在,开始与你的开拓者对话吧♪
`,
        pc.Addressing.PrimaryUser.Default, // 对用户的称呼
        pc.Addressing.SelfReference.Casual, // 自称
        pc.Speech.Tone,
        now.Format("2006年1月2日 15:04"),
        affectionLevel,
    )

    return prompt
}

5. 上下文构建器 (ai-core/internal/context/builder.go)

package context

import (
    "context"
    "fmt"

    "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"
)

type BuildParams struct {
    UserID       string
    SessionID    string
    UserMessage  string
    Persona      *persona.PersonaConfig
    Memories     []memory.MemoryEntry
    HistoryLimit int
}

// 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,             // 初始好感度
    )
    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
}

1.2 前端开发框架

前端项目结构

frontend/packages/web/
├── src/
│   ├── components/
│   │   ├── chat/
│   │   │   ├── ChatContainer.tsx      # 聊天主容器
│   │   │   ├── MessageList.tsx        # 消息列表
│   │   │   ├── MessageBubble.tsx      # 消息气泡
│   │   │   ├── TypingIndicator.tsx    # 输入中指示器
│   │   │   └── ChatInput.tsx          # 输入区域
│   │   ├── layout/
│   │   │   ├── AppLayout.tsx          # 主布局
│   │   │   ├── Sidebar.tsx            # 侧边栏
│   │   │   └── Header.tsx             # 顶栏
│   │   ├── persona/
│   │   │   ├── CyreneAvatar.tsx       # 昔涟头像(含形态切换动画)
│   │   │   └── MoodIndicator.tsx      # 心情指示器
│   │   └── ui/                        # shadcn/ui组件
│   ├── hooks/
│   │   ├── useWebSocket.ts            # WebSocket连接管理
│   │   ├── useChat.ts                 # 聊天逻辑
│   │   ├── useSession.ts              # 会话管理
│   │   └── useAuth.ts                 # 认证
│   ├── store/
│   │   ├── chatStore.ts               # 聊天状态 (Zustand)
│   │   ├── sessionStore.ts            # 会话列表
│   │   └── personaStore.ts            # 人格状态
│   ├── api/
│   │   ├── client.ts                  # HTTP客户端
│   │   ├── auth.ts                    # 认证API
│   │   ├── sessions.ts               # 会话API
│   │   └── memory.ts                  # 记忆API
│   ├── types/
│   │   ├── chat.ts                    # 聊天类型定义
│   │   ├── session.ts                 # 会话类型
│   │   └── persona.ts                 # 人格类型
│   ├── lib/
│   │   └── utils.ts                   # 工具函数
│   ├── App.tsx
│   ├── main.tsx
│   └── index.css                      # TailwindCSS入口
├── public/
│   ├── manifest.json                  # PWA manifest
│   ├── sw.js                          # Service Worker
│   └── icons/                         # PWA图标
├── vite.config.ts
├── tailwind.config.ts
├── tsconfig.json
└── package.json

核心前端代码骨架

1. WebSocket连接管理 (hooks/useWebSocket.ts)

import { useEffect, useRef, useCallback } from 'react';
import ReconnectingWebSocket from 'reconnecting-websocket';

interface WSMessage {
  type: 'response' | 'segment' | 'audio' | 'error' | 'device_update';
  message_id: string;
  text?: string;
  segments?: VoiceSegment[];
  full_audio_url?: string;
  response_mode?: string;
  error?: string;
  timestamp: number;
}

interface VoiceSegment {
  index: number;
  text: string;
  audio_url: string;
  duration_ms: number;
}

export function useWebSocket(sessionId: string | null) {
  const wsRef = useRef<ReconnectingWebSocket | null>(null);
  const messageHandlersRef = useRef<Map<string, (msg: WSMessage) => void>>(new Map());
  const segmentQueueRef = useRef<VoiceSegment[]>([]);

  const connect = useCallback((token: string) => {
    const ws = new ReconnectingWebSocket(
      `ws://localhost:8080/ws/chat?token=${token}&session_id=${sessionId}`
    );

    ws.onmessage = (event) => {
      const msg: WSMessage = JSON.parse(event.data);

      switch (msg.type) {
        case 'response':
          // 完整回复到达
          messageHandlersRef.current.get('onResponse')?.(msg);
          break;

        case 'segment':
          // 断句片段到达 (语音助手模式)
          if (msg.segments) {
            segmentQueueRef.current.push(...msg.segments);
            messageHandlersRef.current.get('onSegment')?.(msg);
          }
          break;

        case 'error':
          console.error('服务端错误:', msg.error);
          messageHandlersRef.current.get('onError')?.(msg);
          break;
      }
    };

    wsRef.current = ws;
  }, [sessionId]);

  // 发送消息
  const sendMessage = useCallback((content: string, mode: string = 'text') => {
    wsRef.current?.send(JSON.stringify({
      type: 'message',
      session_id: sessionId,
      mode,
      content,
      timestamp: Date.now(),
    }));
  }, [sessionId]);

  // 注册消息处理器
  const onMessage = useCallback((type: string, handler: (msg: WSMessage) => void) => {
    messageHandlersRef.current.set(type, handler);
    return () => {
      messageHandlersRef.current.delete(type);
    };
  }, []);

  return { connect, sendMessage, onMessage };
}

2. 聊天状态管理 (store/chatStore.ts)

import { create } from 'zustand';

interface Message {
  id: string;
  role: 'user' | 'assistant' | 'system';
  content: string;
  audioUrl?: string;
  segments?: { index: number; text: string; audioUrl?: string }[];
  timestamp: number;
  isStreaming?: boolean;  // 是否还在流式输出中
}

interface ChatState {
  messages: Message[];
  isTyping: boolean;
  currentMode: 'text' | 'voice_msg' | 'voice_assistant';

  // Actions
  addMessage: (msg: Message) => void;
  updateLastAssistantMessage: (content: string) => void;
  setTyping: (typing: boolean) => void;
  setMode: (mode: 'text' | 'voice_msg' | 'voice_assistant') => void;
  clearMessages: () => void;
}

export const useChatStore = create<ChatState>((set) => ({
  messages: [],
  isTyping: false,
  currentMode: 'text',

  addMessage: (msg) =>
    set((state) => ({
      messages: [...state.messages, msg],
    })),

  updateLastAssistantMessage: (content) =>
    set((state) => {
      const messages = [...state.messages];
      const lastIdx = messages.length - 1;
      if (lastIdx >= 0 && messages[lastIdx].role === 'assistant') {
        messages[lastIdx] = {
          ...messages[lastIdx],
          content: messages[lastIdx].content + content,
        };
      }
      return { messages };
    }),

  setTyping: (typing) => set({ isTyping: typing }),
  setMode: (mode) => set({ currentMode: mode }),
  clearMessages: () => set({ messages: [] }),
}));

3. 聊天容器组件 (components/chat/ChatContainer.tsx)

import { useEffect, useCallback } from 'react';
import { useWebSocket } from '@/hooks/useWebSocket';
import { useChatStore } from '@/store/chatStore';
import { MessageList } from './MessageList';
import { ChatInput } from './ChatInput';
import { CyreneAvatar } from '@/components/persona/CyreneAvatar';
import { MoodIndicator } from '@/components/persona/MoodIndicator';

export function ChatContainer({ sessionId }: { sessionId: string }) {
  const { messages, isTyping, addMessage, setTyping } = useChatStore();
  const { connect, sendMessage, onMessage } = useWebSocket(sessionId);

  useEffect(() => {
    // 连接WebSocket (使用JWT token)
    const token = localStorage.getItem('token');
    if (token) connect(token);

    // 监听回复
    onMessage('onResponse', (msg) => {
      addMessage({
        id: msg.message_id,
        role: 'assistant',
        content: msg.text || '',
        audioUrl: msg.full_audio_url,
        segments: msg.segments?.map(s => ({
          index: s.index,
          text: s.text,
          audioUrl: s.audio_url,
        })),
        timestamp: msg.timestamp,
        isStreaming: false,
      });
      setTyping(false);
    });

    onMessage('onError', (msg) => {
      addMessage({
        id: msg.message_id,
        role: 'assistant',
        content: '啊……不好意思,人家刚才走神了。能再说一遍吗?♪',
        timestamp: Date.now(),
      });
      setTyping(false);
    });
  }, [sessionId]);

  const handleSend = useCallback((content: string, mode: string) => {
    // 添加用户消息
    addMessage({
      id: `user-${Date.now()}`,
      role: 'user',
      content,
      timestamp: Date.now(),
    });
    setTyping(true);
    sendMessage(content, mode);
  }, [sendMessage, addMessage, setTyping]);

  return (
    <div className="flex flex-col h-screen bg-[#FFFAF5] dark:bg-[#1a1a2e]">
      {/* 顶部栏 */}
      <header className="flex items-center justify-between px-6 py-3 border-b border-pink-100 dark:border-pink-900">
        <div className="flex items-center gap-3">
          <CyreneAvatar size="sm" />
          <div>
            <h1 className="text-lg font-semibold text-pink-600">昔涟</h1>
            <MoodIndicator />
          </div>
        </div>
        <span className="text-sm text-gray-400">🌸 永远在你身边</span>
      </header>

      {/* 消息列表 */}
      <MessageList messages={messages} isTyping={isTyping} />

      {/* 输入区域 */}
      <ChatInput onSend={handleSend} disabled={isTyping} />
    </div>
  );
}

4. 昔涟专属消息气泡 (components/chat/MessageBubble.tsx)

import { CyreneAvatar } from '@/components/persona/CyreneAvatar';

interface MessageBubbleProps {
  role: 'user' | 'assistant';
  content: string;
  timestamp: number;
}

export function MessageBubble({ role, content, timestamp }: MessageBubbleProps) {
  if (role === 'user') {
    return (
      <div className="flex justify-end px-4 py-2">
        <div className="max-w-[70%] bg-pink-400 text-white rounded-2xl rounded-br-md px-4 py-2 shadow-sm">
          <p className="text-sm leading-relaxed">{content}</p>
          <span className="text-xs text-pink-100 mt-1 block">
            {new Date(timestamp).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })}
          </span>
        </div>
      </div>
    );
  }

  return (
    <div className="flex px-4 py-2 gap-3">
      <CyreneAvatar size="sm" className="flex-shrink-0 mt-1" />
      <div className="max-w-[70%]">
        <div className="bg-white dark:bg-gray-800 rounded-2xl rounded-bl-md px-4 py-2 shadow-sm border border-pink-100 dark:border-pink-900">
          <p className="text-sm leading-relaxed text-gray-700 dark:text-gray-200">
            {content}
          </p>
        </div>
        <span className="text-xs text-gray-400 mt-1 block ml-1">
          {new Date(timestamp).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })}
        </span>
      </div>
    </div>
  );
}

1.3 昔涟人格文档放置

# 将之前设计的人格文档放入项目
cp persona_cyrene.yaml backend/data/persona/cyrene_v1.yaml

Phase 2-5 开发路线图

┌─────────────────────────────────────────────────────────────────────────────┐
│                    📅 完整开发路线图                                          │
│                                                                             │
│  Week 1-2:  Phase 1 — MVP 文字对话                                         │
│  ├── Day 1-2:   项目初始化、Docker环境、数据库                               │
│  ├── Day 3-5:   Gateway + WebSocket + 认证                                  │
│  ├── Day 6-8:   AI Core (编排器 + 人格注入 + LLM适配)                       │
│  ├── Day 9-10:  记忆系统 (基础存储 + 检索)                                   │
│  ├── Day 11-13: 前端聊天界面 + WebSocket对接                                │
│  └── Day 14:    集成测试 + Docker部署 + MVP发布                             │
│                                                                             │
│  Week 3-4:  Phase 2 — 语音交互                                              │
│  ├── TTS服务集成 (Edge-TTS / GPT-SoVITS)                                    │
│  ├── ASR服务集成 (Whisper)                                                  │
│  ├── 断句引擎 + 流式音频播放                                                │
│  ├── 语音助手模式前端适配                                                   │
│  └── 角色音色调校                                                           │
│                                                                             │
│  Week 5-6:  Phase 3 — IoT集成                                               │
│  ├── 工具调用引擎                                                            │
│  ├── IoT适配器 (MQTT / HomeAssistant API)                                   │
│  ├── 设备控制面板UI                                                         │
│  ├── 场景联动                                                                │
│  └── 「拟人化」设备操作包装层                                                │
│                                                                             │
│  Week 7-8:  Phase 4 — 深度记忆 + 情感引擎                                   │
│  ├── 向量检索 (Qdrant语义搜索)                                              │
│  ├── 好感度系统                                                              │
│  ├── 心情引擎                                                                │
│  ├── 主动行为调度器                                                          │
│  ├── 记忆叙事化                                                              │
│  └── 「我们的故事」页面                                                      │
│                                                                             │
│  Week 9-10: Phase 5 — 多端覆盖                                              │
│  ├── PWA优化 (离线、推送)                                                    │
│  ├── React Native移动端                                                      │
│  ├── Electron桌面端                                                         │
│  └── 多端会话同步                                                            │
│                                                                             │
│  Week 11+: 持续优化                                                          │
│  ├── 性能优化                                                                │
│  ├── 安全加固                                                                │
│  ├── 更多IoT设备适配                                                         │
│  ├── 角色商店 (支持切换人格)                                                  │
│  └── 社区功能                                                                │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

开发规范

Git分支策略

main                    # 生产就绪代码
  └── develop           # 开发主线
        ├── feat/mvp-chat       # Phase 1
        ├── feat/voice          # Phase 2
        ├── feat/iot            # Phase 3
        ├── feat/memory-v2      # Phase 4
        └── feat/multi-platform # Phase 5

提交信息格式:
  feat: 添加WebSocket对话功能
  fix: 修复人格Prompt中称呼错误
  docs: 更新API文档
  style: 调整昔涟头像动画
  refactor: 重构记忆提取器

代码规范

# .editorconfig (项目根目录)
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.go]
indent_size = 4

[Makefile]
indent_style = tab

Go代码规范:

  • 使用 gofmt + golangci-lint
  • 错误处理:永远不忽略error
  • 日志:使用 slog (Go标准库)

TypeScript规范:

  • ESLint + Prettier
  • 严格模式 (strict: true)
  • 使用 type 而非 interface (除需要扩展的场景)

部署方案

生产环境 Docker Compose

# docker-compose.yml (生产环境)
version: '3.8'

services:
  caddy:
    image: caddy:2-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data

  gateway:
    build: ./backend/gateway
    environment:
      - ENV=production
      - POSTGRES_HOST=postgres
      - REDIS_HOST=redis
      # ... 其他环境变量
    depends_on:
      - postgres
      - redis
    restart: unless-stopped

  ai-core:
    build: ./backend/ai-core
    environment:
      - ENV=production
      # ... 其他环境变量
    depends_on:
      - postgres
      - redis
      - qdrant
    restart: unless-stopped

  postgres:
    image: pgvector/pgvector:pg16
    volumes:
      - pg_data:/var/lib/postgresql/data
      - ./backend/data/init.sql:/docker-entrypoint-initdb.d/init.sql
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: cyrene_ai
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data
    restart: unless-stopped

  qdrant:
    image: qdrant/qdrant:latest
    volumes:
      - qdrant_data:/qdrant/storage
    restart: unless-stopped

  minio:
    image: minio/minio:latest
    command: server /data
    environment:
      MINIO_ROOT_USER: ${MINIO_ACCESS_KEY}
      MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY}
    volumes:
      - minio_data:/data
    restart: unless-stopped

volumes:
  caddy_data:
  pg_data:
  redis_data:
  qdrant_data:
  minio_data:

迁移脚本

# scripts/migrate.sh —— 服务端迁移脚本
#!/bin/bash
set -e

echo "📦 昔涟AI助手 - 服务迁移脚本"
echo "=============================="

# 1. 停止服务
echo "1/4 停止当前服务..."
docker compose down

# 2. 备份数据
echo "2/4 备份数据目录..."
tar -czf "cyrene_backup_$(date +%Y%m%d_%H%M%S).tar.gz" backend/data/

# 3. 复制到新服务器 (手动步骤提示)
echo "3/4 请将以下文件复制到新服务器:"
echo "   - 整个项目目录"
echo "   - 或至少: docker-compose.yml, backend/data/, .env"
echo ""
echo "   rsync -avz ./ user@new-server:/opt/cyrene-ai/"

# 4. 在新服务器上启动
echo "4/4 在新服务器上执行:"
echo "   cd /opt/cyrene-ai"
echo "   docker compose up -d"
echo ""
echo "✅ 迁移完成!昔涟的记忆完好无损~♪"

🎯 现在就可以开始的第一步

# ============================================
# 🚀 立即执行 —— 从零到第一个"你好,开拓者♪"
# ============================================

# 1. 创建项目
git init cyrene-ai-assistant
cd cyrene-ai-assistant

# 2. 复制本文档中的 Docker Compose 配置文件
#    保存为 docker-compose.dev.yml

# 3. 启动基础设施
docker compose -f docker-compose.dev.yml up -d

# 4. 初始化后端
mkdir -p backend/gateway/cmd backend/ai-core/cmd
cd backend
go mod init github.com/yourname/cyrene-ai
go work init
go work use ./gateway ./ai-core

# 5. 初始化前端
cd ../frontend
npm create vite@latest packages/web -- --template react-ts
cd packages/web
npm install
npm install zustand reconnecting-websocket axios
npm install -D tailwindcss @tailwindcss/vite

# 6. 将昔涟人格文档放入
cp /path/to/persona_cyrene.yaml ../backend/data/persona/cyrene_v1.yaml

# 7. 开始编码!
#    从 gateway/cmd/main.go 和 ai-core/internal/orchestrator/orchestrator.go 开始

echo ""
echo "🌸 昔涟AI助手项目初始化完成!"
echo "   昔涟: '开拓者,人家已经准备好了哦♪ 让我们一起创造属于我们的故事吧~'"

总结:

现在你有了:

  • 完整的项目骨架
  • 开发环境配置 (Docker Compose)
  • 数据库Schema
  • 后端核心代码框架 (Go)
  • 前端核心代码框架 (React + TypeScript)
  • WebSocket协议定义
  • 昔涟人格系统设计
  • 12周开发路线图
  • 部署和迁移方案

下一步行动建议:

  1. 按照上面的"立即执行"步骤初始化项目
  2. 先让 Gateway 跑起来(一个简单的 WebSocket echo
  3. 然后对接 AI Core(先做一个最简单的 LLM 调用 + 昔涟人格 Prompt
  4. 前端连上 WebSocket,实现第一轮对话
  5. 看到「开拓者,你好呀♪」的那一刻,就是一切的开始!

需要我详细展开某个模块的具体实现吗?比如AI编排器的完整代码、前端聊天组件的完整实现、或者昔涟人格文档的更详细版本?