Intro
LAPLACE Eidolon 是主播人格的语音分身——访客通过浏览器进入「拉普拉斯花店」,与一台会说话、会记事的 AI 实时对话
技术上是一台基于 Pipecat 的级联式语音机器人,串联 STT、LLM 与 TTS,并叠加可选的多层 RAG 与每位观众的长期记忆,让分身能够「记得」过往直播片段、人设设定,以及与每位访客之间的私人交流
在线演示
laplace.live 上的 AI 聊天页面即由本项目驱动,打开即可与分身对话:
管线结构
transport.input → STT → user_aggregator
→ RetrievalGate(判定本轮是否需要 RAG,可选)
→ Mem0(每位观众的长期记忆,可选)
→ StreamArchive(往期直播 RAG,可选)
→ PersonaFacts(人设事实 RAG,可选)
→ LLM → TTS → transport.output → assistant_aggregator技术栈
- STT:Soniox(
stt-rt-v5) - LLM:OpenRouter(默认
~google/gemini-flash-latest) - TTS:Fish Audio(
s2.1-pro) - 传输:SmallWebRTC(Pipecat 自托管)
- 向量库:LanceDB(本地)
- 长期记忆:自托管 Mem0 + Qdrant(可选)
仅支持自托管 SmallWebRTC,依赖自有反向代理终止 TLS,不接入 Daily 或 Pipecat Cloud
四层人设记忆
机器人通过四层持久化存储为 LLM 注入上下文,每一层都可独立启停,仅启用 persona.md 的最小配置也能稳定运行:
- persona.md:始终生效的身份、语气与硬性规则,启动时直接载入 system prompt
- Persona facts:手工整理的「她说过的话」原子事实,按语义相关度检索(LanceDB 索引由
laplace-ingest-facts构建) - Stream archive:经过 LLM 二次精炼的往期直播片段,按语义相关度检索(LanceDB 索引由
laplace-ingest-streams构建),并附带一层整场概览的场次目录 - Mem0:从对话中自动抽取的每位观众情景记忆,按 Bilibili UID 隔离;以 Qdrant 承载向量,作为库嵌入机器人进程运行
混合检索与重排
Persona facts 与 Stream archive 两层 RAG 共用一条混合检索管线,每轮对话实时执行:
- 构造查询:综合最近几轮用户发言,使短促语音(如「嗯,那呢?」)也能借上下文检索
- 统一向量化:以
EMBED_MODEL(默认 Gemini,3072 维)嵌入一次查询 - 日期预过滤(仅 persona facts):查询提及日期(
2026年5月9日/5月9号)时,先按date列收窄候选集 - 混合搜索:向量检索叠加 jieba 分词的中文 BM25/FTS,拉取
PERSONA_FACTS_CANDIDATES(默认 30)或STREAM_ARCHIVE_CANDIDATES(默认 15)条候选,并剥离停用词以防 BM25 分数塌缩 - 交叉编码重排:默认经 OpenRouter 调用
cohere/rerank-4-fast精排至top_k(facts 取 8、chunks 取 3),复用 LLM 所需的OPENROUTER_API_KEY;RERANK_ENABLED=false关闭后回退 LanceDB 的 RRF 融合
数据规模约 1 万行以上时重排是精度的最大杠杆,成本约 $0.002 /
次检索。RERANK_BASE_URL、RERANK_API_KEY 可切回 Cohere
直连(更便宜且无需重试);laplace-eval-retrieval 可量化 recall@k /
MRR,对比改动前后的回归
检索闸门
并非每轮对话都需要查库——「我今天加班好累啊」这类闲聊照样要在两张 LanceDB 表上各跑一次嵌入与重排,在实时通话里白白多出 4~5 秒。RetrievalGate 位于用户聚合器之后、Mem0 之前,每个新回合向 TypeSafe 的 Jev 决策模型问三件事:这句话是否问到了主播本人(喜好、习惯、宠物、朋友、花店……)、她过去说过做过的事,或某个具名的人事物。任意一问的置信度达到 0.3 即判定需要检索
闸门只取消检索,从不延后检索:两层 RAG 收到帧后立即开始搜索,仅当 Jev 先一步答出「跳过」才撤销。先等闸门再搜索,会给每个真正需要回忆的回合平白多加约 0.5 秒;超时、报错或任何非明确否定的结果,一律按「需要检索」处理
正则路由的几条分支——限定日期的回忆、整场回顾、recall_streams 工具——本就知道问题与直播有关,不经闸门;近期开播概览与 Mem0 同样照常运行
在 185 条人工标注的回合上测得:93 条需要回忆的全部保留,92 条闲聊跳过 72
条,每轮成本约 $0.00001。默认模型 ~typesafe/jev-latest
会随版本更新移动校准,需要固定时改用
typesafe/jev-1.13;RETRIEVAL_GATE_ENABLED=false 恢复每轮都查库
直播回忆与场次目录
除按语义检索约 5 分钟的直播片段外,Stream archive 还维护一份场次目录(stream_catalog.json,由 laplace-build-stream-catalog 从已精炼的转录本地构建,不产生任何模型或嵌入请求)。目录按日期与 BV 号归组录播分段,为每场直播保留活动统计、代表性摘录与全部场景摘要
有了这一层,分身能区分「整场回顾」与「某个话题」两类问题:
- 整场回顾(「上周直播都播了什么」)直接读取匹配的目录记录,跳过嵌入、重排与人设事实检索
- 限定时间的话题查询沿用混合检索,并叠加一层元数据预过滤
- 复杂或需要模型判断的时间指代,交由
recall_streams工具处理:它接受latest、last_week、past_days、date_range等时段,或上下文中已给出的场次 ID;每个用户回合最多两次查询
日期与时间段按 PERSONA_TIMEZONE 解析——「上周」指上一个周一至周日,「最近七天」含今天。目录缺失或不可读时静默降级,原有的片段检索照常工作
自托管
仅需运行:
docker compose up --build镜像内置 Pipecat 运行时与已构建的 LanceDB 索引,监听 7860 端口;端点 POST /api/offer 处理 SmallWebRTC SDP 信令,前端可直接对接,亦可使用 @pipecat-ai/client-js 与 @pipecat-ai/small-webrtc-transport
生产部署需在反向代理中终止 TLS,并按需配置 TURN 以穿透对称 NAT
推荐使用 Cloudflare Realtime TURN,机器人会按需签发凭据并自动轮换(前端经 GET /api/ice-servers 取用),免费额度约 1 TB/月中继流量,覆盖大多数自部署场景
配置 TURN 后服务端只收集中继候选:源站的公网 IP 不再出现在观众的 SDP 中,各类 NAT 也都能在第一个候选上连通,代价是全部媒体走中继、TURN 成为硬依赖。仅配置 STUN 时维持原有行为
HTTPS 是浏览器获取麦克风权限的硬性要求,本地 localhost
之外的任何环境都需配置有效证书
观众身份与限流
可选的 loginSyncToken 校验由 LAPLACE Login Sync 完成:识别出的 Bilibili UID 用作 Mem0 命名空间,并跳过匿名限流;未携带或校验失败的访客则按 IP 进入滑动窗口配额,默认每小时 5 次连接尝试
登录访客还可自助管理属于自己的 Mem0 记忆——GET /api/memories 列出、POST /api/memories/forget 清除,二者均以 WebRTC 握手所携带的同一 loginSyncToken 鉴权,并严格限定在调用者本人的 Bilibili UID 范围内
运行时声线切换
分身可在通话中切换 Fish Audio 声线:当访客说出「温柔一点」「活泼一点」等口令时,change_voice_style 工具实时换用对应的 reference_id。配置 FISH_VOICE_ID_GENTLE、FISH_VOICE_ID_LIVELY 即可启用,全部留空则关闭该能力
此外,前端可按音频质量逐会话选择 Opus 编码码率(EIDOLON_OPUS_BITRATE_*,默认 96 kbps),在弱网与音质之间权衡
自拍
访客说出「拍张自拍给我看看」时,take_selfie 工具调用图像模型重绘一张参考图,成片在通话中直接出现在对话里。默认开启:参考图随 Docker 镜像一并打包在 data/selfie-references/,清空该目录即不再注册这个工具
outfits/是同一个人的不同装束,每次出图只取一张——同时给出两张,模型会把第二张读成第二个人。取哪张按PERSONA_TIMEZONE的当前时刻决定(21:00–05:00 取night.png),与提示词描述的光线保持一致friends/每人一张,文件名即人名,并原样成为工具参数with_friends的枚举值。点名同框会把场景移到户外,并改用当前时刻的室外光- 成片约 1 MB,远超 WebRTC 数据通道的消息上限,因此存入进程内的 TTL 缓存,仅以
GET /api/selfie/<id>.png的地址经 RTVIserver-message({ "type": "selfie", "urls": [...] })推给前端。ID 本身即凭据——不可猜测、单次用途、到期即失效
出图约十余秒,因此该工具以异步方式注册:LLM 不会卡在这一轮,而是先说一句「等我一下」,成片稍后作为 developer message 注入。SELFIE_MODEL(默认 openai/gpt-image-2.5-flare)、SELFIE_ASPECT_RATIO(默认 3:4)、SELFIE_QUALITY、SELFIE_TTL_SECONDS(默认 600)与 SELFIE_TIMEOUT_SECONDS(默认 120)可分别调整
离线的 laplace-generate-image 与机器人共用同一段生成逻辑和同一套参考图,改动提示词后可先在命令行验证成片
公共 MCP 服务
机器人在 /mcp/ 额外暴露一个只读的 Model Context Protocol 端点(Streamable HTTP),向任意 MCP 客户端开放与语音管线相同的两层 RAG 索引——search_persona_facts 与 search_streams。公共实例位于 https://eidolon.vrp.moe/mcp,默认匿名开放。详见 MCP 服务
离线工具
构建索引、批量下载与转录均以独立 console script 提供,全部通过 uv run 调用:
| 命令 | 功能 |
|---|---|
laplace-pipeline | 端到端流水线:下载 → 转录 → 精炼 → 入库 |
laplace-download | 基于 yt-dlp 的批量 VOD 下载 |
laplace-transcribe | Whisper / Soniox 转录 |
laplace-refine | 将原始转录精炼为结构化记忆 JSON |
laplace-ingest-streams | 构建 stream_chunks LanceDB 索引 |
laplace-ingest-facts | 构建 persona_facts LanceDB 索引 |
laplace-build-stream-catalog | 构建场次目录 stream_catalog.json |
laplace-search | LanceDB 索引的 REPL 式检索 |
laplace-eval-retrieval | 量化检索 recall@k / MRR,回归对比基线 |
laplace-extract-users | 从弹幕 JSON 中挖掘高频用户名 |
laplace-export-mem0 | 将 Mem0 云端账户导出为 JSON |
laplace-import-mem0 | 将导出的 JSON 幂等导入 Qdrant |
laplace-generate-image | 由提示词与参考图生成图片,与自拍同源 |
源代码
最終更新日:2026年9月20日