00:00 / 00:00

Eidolon

LAPLACE Eidolon

簡介

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

技術堆疊

  • STTSonioxstt-rt-v5
  • LLMOpenRouter(預設 ~google/gemini-flash-latest
  • TTSFish Audios2.1-pro
  • 傳輸:SmallWebRTC(Pipecat 自架)
  • 向量庫:LanceDB(本機)
  • 長期記憶:自行架設的 Mem0 + Qdrant(可選)

僅支援自行架設的 SmallWebRTC,依賴自有反向代理終止 TLS,不接入 Daily 或 Pipecat Cloud

四層人設記憶

機器人透過四層持久化儲存為 LLM 注入上下文,每一層都可獨立啟停,僅啟用 persona.md 的最小設定也能穩定執行:

  1. persona.md:恆常生效的身分、語氣與硬性規則,啟動時直接載入 system prompt
  2. Persona facts:手工整理的「她說過的話」原子事實,依語意相關度檢索(LanceDB 索引由 laplace-ingest-facts 建立)
  3. Stream archive:經過 LLM 二次精煉的往期直播片段,依語意相關度檢索(LanceDB 索引由 laplace-ingest-streams 建立),並附帶一層整場概覽的場次目錄
  4. Mem0:從對話中自動擷取的每位觀眾情景記憶,依 Bilibili UID 隔離;以 Qdrant 承載向量,作為函式庫嵌入機器人行程執行

混合檢索與重排

Persona facts 與 Stream archive 兩層 RAG 共用一條混合檢索管線,每輪對話即時執行:

  1. 建構查詢:綜合最近幾輪使用者發言,讓短促語音(如「嗯,那呢?」)也能藉上下文檢索
  2. 統一向量化:以 EMBED_MODEL(預設 Gemini,3072 維)嵌入一次查詢
  3. 日期預過濾(僅 persona facts):查詢提及日期(2026年5月9日 / 5月9号)時,先依 date 欄收窄候選集
  4. 混合搜尋:向量檢索疊加 jieba 斷詞的中文 BM25/FTS,拉取 PERSONA_FACTS_CANDIDATES(預設 30)或 STREAM_ARCHIVE_CANDIDATES(預設 15)筆候選,並剝離停用詞以免 BM25 分數塌陷
  5. 交叉編碼重排:預設經 OpenRouter 呼叫 cohere/rerank-4-fast 精排至 top_k(facts 取 8、chunks 取 3),沿用 LLM 所需的 OPENROUTER_API_KEY;設為 RERANK_ENABLED=false 後回退 LanceDB 的 RRF 融合

檢索閘門

並非每輪對話都需要查庫——「我今天加班好累啊」這類閒聊照樣要在兩張 LanceDB 表上各跑一次嵌入與重排,在即時通話裡白白多出 4~5 秒。RetrievalGate 位於使用者聚合器之後、Mem0 之前,每個新回合向 TypeSafe 的 Jev 決策模型問三件事:這句話是否問到了主播本人(喜好、習慣、寵物、朋友、花店……)、她過去說過做過的事,或某個具名的人事物。任一問的信賴度達到 0.3 即判定需要檢索

閘門只取消檢索,從不延後檢索:兩層 RAG 收到影格後立即開始搜尋,僅當 Jev 先一步答出「略過」才撤銷。先等閘門再搜尋,會給每個真正需要回憶的回合平白多加約 0.5 秒;逾時、報錯或任何非明確否定的結果,一律按「需要檢索」處理

正規表示式路由的幾條分支——限定日期的回憶、整場回顧、recall_streams 工具——本就知道問題與直播有關,不經閘門;近期開播概覽與 Mem0 同樣照常運作

直播回憶與場次目錄

除了依語意檢索約 5 分鐘的直播片段,Stream archive 還維護一份場次目錄(stream_catalog.json,由 laplace-build-stream-catalog 從已精煉的轉錄在本機建立,不產生任何模型或嵌入請求)。目錄依日期與 BV 號歸組錄影分段,為每場直播保留活動統計、代表性摘錄與全部場景摘要

有了這一層,分身能區分「整場回顧」與「某個話題」兩類問題:

  • 整場回顧(「上週直播都播了什麼」)直接讀取符合的目錄記錄,略過嵌入、重排與人設事實檢索
  • 限定時間的話題查詢沿用混合檢索,並疊加一層中繼資料預過濾
  • 複雜或需要模型判斷的時間指代,交由 recall_streams 工具處理:它接受 latestlast_weekpast_daysdate_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

設定 TURN 後伺服器端只收集中繼候選:來源站的公開 IP 不再出現在觀眾的 SDP 中,各類 NAT 也都能在第一個候選上連通,代價是全部媒體走中繼、TURN 成為硬相依。僅設定 STUN 時維持原有行為

觀眾身分與限流

可選的 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_GENTLEFISH_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 的位址經 RTVI server-message{ "type": "selfie", "urls": [...] })推給前端。ID 本身即憑證——無法猜測、單次用途、到期即失效

產圖約十餘秒,因此該工具以非同步方式註冊:LLM 不會卡在這一輪,而是先說一句「等我一下」,成品稍後作為 developer message 注入。SELFIE_MODEL(預設 openai/gpt-image-2.5-flare)、SELFIE_ASPECT_RATIO(預設 3:4)、SELFIE_QUALITYSELFIE_TTL_SECONDS(預設 600)與 SELFIE_TIMEOUT_SECONDS(預設 120)可分別調整

離線的 laplace-generate-image 與機器人共用同一段產生邏輯和同一套參考圖,改動提示詞後可先在命令列驗證成品

公共 MCP 服務

機器人在 /mcp/ 額外開放一個唯讀的 Model Context Protocol 端點(Streamable HTTP),向任意 MCP 用戶端提供與語音管線相同的兩層 RAG 索引——search_persona_factssearch_streams。公開實例位於 https://eidolon.vrp.moe/mcp,預設匿名開放。詳見 MCP 服務

離線工具

建立索引、批次下載與轉錄均以獨立 console script 提供,全部透過 uv run 呼叫:

指令功能
laplace-pipeline端到端管線:下載 → 轉錄 → 精煉 → 入庫
laplace-download基於 yt-dlp 的批次 VOD 下載
laplace-transcribeWhisper / Soniox 轉錄
laplace-refine將原始轉錄精煉為結構化記憶 JSON
laplace-ingest-streams建立 stream_chunks LanceDB 索引
laplace-ingest-facts建立 persona_facts LanceDB 索引
laplace-build-stream-catalog建立場次目錄 stream_catalog.json
laplace-searchLanceDB 索引的 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日

Tech otakus destroy the world