VoiceIT Session / Memory / Realtime 跨團隊說明報告

查核專案:euis-voiceitadmin-ui、euis-voiceitmcpsv、euis-voiceitsv、euis-voiceit-ui;產出時間:2026-05-28

1

Session 和 Memory 分別存哪些資料?

分三層看:短期 Session 在服務記憶體,持久紀錄在資料庫,音檔目前預設不保存。

層級 存放位置 內容
Session(短期) 後端服務記憶體 + 即時模型 session 通話識別、對話識別、使用者身分、即時連線與工具呼叫狀態;目前未啟用錄音保存時,不會累積錄音資料。
Memory(持久紀錄) PostgreSQL / Cloud SQL 類型資料庫 通話主檔、對話事件、逐字稿、安全護欄事件、報修/工單關聯。
音訊檔 依部署策略決定:不保存 / 本機 / GCS(Google Cloud Storage) 目前預設不保存音檔;只有明確啟用本機或 GCS(Google Cloud Storage)保存時,才會保存整通 WAV。

⚠️ 名詞提醒

目前系統沒有「會自動讀回下一次對話」的 LLM 長期記憶。現在保存的是通話與事件紀錄,不是會影響下一次對話的個人化記憶。

2

怎麼監聽一次對話完全結束?

以使用者通話連線關閉後,後端完成收尾為準。AI 的單次回答完成不代表整通對話結束。

🔄 收尾時會做什麼?

  • 關閉即時模型連線。
  • 依音檔保存策略決定是否產生錄音檔。
  • 標記通話已完成。
  • 抽取報修單 / 工單關聯,並清理工具連線狀態。

💡 建議

若其他團隊要監聽「整通對話結束」,建議由後端在收尾完成後發出明確事件,不要直接監聽單一 AI 回覆事件。

3

Memory 儲存頻率?是整通結束才寫一次嗎?

不是整通結束才一次寫入。不同資料有不同寫入節奏。

資料 寫入時機 說明
對話事件 / 逐字稿 即時、逐筆寫入 使用者與 AI 的即時互動會邊講邊記錄。
安全護欄事件 命中時寫入 使用者輸入或 AI 輸出命中安全規則時記錄。
音檔 / 工單關聯 對話結束收尾時 音檔只有在明確啟用保存時才會產生;目前預設不保存錄音。
4

Realtime 語音內容要用什麼資料型態存?

原則是:大型二進位不要進資料庫。語意內容用 JSON 結構保存,音訊若要保存才轉成 WAV 放物件儲存或本機。

內容 建議 / 目前做法 原因
對話事件 / 逐字稿 以 JSON 結構存入資料庫 保留 Realtime event 原始結構,方便查詢與稽核。
音訊 base64 chunk 寫入資料庫前移除或遮蔽 避免 DB 體積膨脹與敏感音訊落庫。
整通錄音 WAV 預設不保存;啟用時存 GCS(Google Cloud Storage)或本機,資料庫只存路徑 音檔屬大型二進位,應由檔案/物件儲存承接。

✅ 目前安全預設

目前預設不保存錄音:不存 GCS(Google Cloud Storage)、不寫本機硬碟、資料庫的音檔路徑保持空值,避免本機硬碟塞爆。

🪣 若未來要啟用 GCS(Google Cloud Storage)

需先建立 bucket、設定服務帳號權限、部署環境明確啟用音檔保存,並搭配 GCS(Google Cloud Storage)生命週期規則控制保存期限。

5

有用到 guardrail 嗎?

有,而且不是只有資料表。安全護欄已接在即時對話流程中。

🛡️ 目前檢查範圍

  • 使用者輸入 transcript。
  • 使用者對話內容。
  • AI 回覆片段。
  • AI 完整回覆。

🚫 觸發後動作

記錄護欄事件、取消 AI 回覆,並中斷使用者端與模型端連線。

6

API key 明文上線疑慮怎麼處理?

正式環境不可依賴 repo 內明文或預設測試值。所有 secret 應由部署環境注入。

類型 建議做法
OpenAI / Gemini key GCP Secret Manager、K8s Secret 或 CI/CD env 注入。
DB / Redis password 避免寫入 repo;由 runtime secret 掛載。
JWT / HS256 / TOTP secret 正式環境應 fail-fast 檢查,不允許 placeholder 或測試值啟動。
GCP 認證 優先使用 Workload Identity,避免下載 service account key 檔。
7

文字模式跟語音模式走同一條 LLM 鏈嗎?

在同一通即時對話內,是同一條鏈。文字與語音共用同一組提示詞、工具能力與安全護欄。

🔀 差異只在輸入事件

  • 語音:送音訊片段給即時模型。
  • 文字:送文字訊息給同一個即時模型 session。
8

Session 短期記憶存在 BigQuery、Cloud SQL 還是後端?

短期記憶在後端服務記憶體與即時模型連線中。不是 BigQuery,也不是 Cloud SQL。

技術 目前用途
BigQuery 目前四個主要專案未看到使用。
Cloud SQL / PostgreSQL 存持久化通話紀錄與事件,不存通話中的短期狀態。
Redis 登入 session 可能使用;即時通話狀態目前沒有用 Redis。
後端服務記憶體 存通話中的短期狀態、即時模型連線與工具呼叫對應關係。

⚠️ 高可用提醒

短期狀態綁在單一服務節點;若未來多副本部署或節點重啟,要評估狀態外移或補償機制。

開發者摘要

目前可確認:系統有持久化「通話紀錄與事件」,但沒有獨立的 LLM 長期 memory 實作。Realtime 對話期間的短期狀態主要在 OpenAI Realtime WebSocket session、euis-voiceitsv JVM memory、BFF WebSocket proxy memory 與前端 Vue state。可查詢歷史紀錄落在 PostgreSQL datasource 的 realtime_connectionsrealtime_eventsguardrail_events

音檔保存現在由 OPENAI_REALTIME_AUDIO_STORAGE_MODE 明確控制。預設 none,不存 GCS(Google Cloud Storage)、也不寫本機;只有設定 localgcs 才會保存整通 WAV。

1. Session 與 Memory 實作邊界

名詞建議:後續文件請避免把 realtime_events 直接稱為 LLM memory。它比較精準的名稱是「對話事件紀錄」或「conversation event log」。若要做可被下一次對話讀回的長期記憶,需要另外設計 memory pipeline。

類型 資料內容 目前位置 持久化 開發注意事項
Realtime short-term session callIdconversationId、使用者身分、上游連線、MCP session id;只有音檔保存啟用時才累積音訊 buffer。 RealtimeBridgeWebSocketHandlerRealtimeConnectionLifecycleManagerToolCallProxyServiceImpl 服務重啟或節點切換會遺失;多副本高可用需評估外部狀態儲存。
BFF HTTP session CXLW / Spring Security session、employeeIdemployeeName euis-voiceit-uieuis-voiceitadmin-ui;非 dev profile 有 Redis Session 設定。 依環境 這是登入 session,不是 Realtime LLM 短期記憶。
前端對話畫面狀態 使用者訊息、AI streaming text、卡片資料、倒數 modal 狀態。 chatAssistant/index.vueuseChatEventHandler.ts 頁面刷新會消失;正式紀錄應以後端 event log 為準。
持久化通話紀錄 callIdprincipalIddeviceId、model、status、started/ended time、audio path。 realtime_connections / RealtimeConnection 查詢列表與通話狀態應以此表為主。
持久化對話事件 OpenAI Realtime event JSON、transcript/text、function/MCP output、token usage 等。 realtime_events.event_data JSONB / RealtimeEvent 音訊 base64 與 PII 會在寫入前處理;不要把此表當作可直接餵回 LLM 的 memory。
Guardrail event callId、INPUT/OUTPUT、category、reason、action、triggeredAt。 guardrail_events / GuardrailEvent 目前不保存觸發原文片段,適合稽核 metadata。

2. 對話生命週期與完整結束判斷

整通對話的生命週期由 WebSocket 連線控制。OpenAI 的 response.done 是單次回覆事件,不應用來判斷通話已結束。

前端建立 Realtime WebSocket BFF 檢查 session / 簽 backend token SV 建立 callId 連 OpenAI Realtime 逐事件寫 DB WebSocket close 收尾
  • RealtimeBridgeWebSocketHandler.onClientConnectionClosed 關閉 upstream WebSocket。
  • 若音檔保存啟用,取出 client/upstream audio buffer,混成 24kHz stereo 16-bit WAV。
  • AudioStorageService.saveAudioaudio-storage-mode 決定是否存本機或 GCS(Google Cloud Storage);預設 none 時不輸出音檔。
  • 只有取得有效音檔路徑時,才由 RealtimeConnectionLifecycleManager.saveAudioPath 寫回 realtime_connections.audio_path
  • RealtimeConnectionLifecycleManager.onConnectionClosed 標記 COMPLETED、關聯報修/工單、清理 MCP sessions。

開發建議:若其他模組要監聽「通話已完整結束」,建議在 onConnectionClosed 完成通話狀態更新、選擇性音檔 path 寫入與 MCP 清理後發布 domain event,或使用 outbox table。

3. 寫入頻率與資料一致性

資料 寫入時機 服務 / 方法 一致性注意事項
realtime_connections WebSocket 建立時新增;開始時標 IN_PROGRESS;close 時標 COMPLETED RealtimeConnectionLifecycleManagerRealtimeConnectionServiceImpl 若 close 流程未跑完,狀態可能卡住;建議加 timeout/reconciler。
realtime_events Realtime event 到達時非同步逐筆寫入。 RealtimeEventServiceImpl.saveEventAsync 非同步寫入失敗只 log warn;若有稽核要求,需補 retry/outbox。
guardrail_events guardrail 命中時非同步寫入。 GuardrailEventServiceImpl.recordAsync 目前保存 metadata,不保存 content snippet。
音檔 WebSocket close 時一次性輸出 WAV。 RealtimeBridgeWebSocketHandlerAudioStorageService 預設不輸出音檔;只有 local / gcs 模式需處理保存失敗補償。
MCP session cache 通話建立時預熱;tool call 時 lazy initialize;通話結束時 remove。 ToolCallProxyServiceImpl.callSessions per-call in-memory cache,不跨節點。

4. Realtime 語音與 GCS(Google Cloud Storage)設定

重點修正:程式支援 GCS(Google Cloud Storage),但目前不應直接視為已啟用。最新預設是 OPENAI_REALTIME_AUDIO_STORAGE_MODE=none,不保存音檔、不寫本機 logs/audio,也不寫 audio_path

目前 GCP 環境實查結果

檢查項目 結果 判讀
目前 GCP project test-voiceit-cxl 本機 gcloud config get-value project 指向此 project。
Storage 相關 API 部分可用 本機查到 storage-api.googleapis.comstorage-component.googleapis.comiamcredentials.googleapis.com 可用;仍需由 infra 確認正式 Cloud Storage API 狀態與 runtime 權限。
可見 GCS(Google Cloud Storage)bucket 未看到 gcloud storage buckets list 未列出可見 bucket;可能尚未建立,或目前帳號沒有看到 bucket 的權限。
目前登入帳號 project-level IAM 未看到可見角色 未看到目前登入帳號在 project-level 有 storage / owner / editor / serviceAccountTokenCreator 相關角色;這不等於 runtime service account,仍需 infra 確認。
本機 GCS(Google Cloud Storage)env 未設定 OPENAI_REALTIME_AUDIO_STORAGE_MODEOPENAI_REALTIME_GCS_ENABLEDOPENAI_REALTIME_GCS_BUCKETOPENAI_REALTIME_GCS_PROJECT_IDGCP_PROJECT_ID 目前未設定;程式預設會落在 none
repo 部署設定 未看到啟用證據 euis-voiceitsv repo 未看到 deployment/Secret/env 設定將 OPENAI_REALTIME_AUDIO_STORAGE_MODE=gcs、bucket 或相關 GCS(Google Cloud Storage)env 注入 runtime。
目前結論 GCS(Google Cloud Storage)儲存尚未確認可用 VoiceIT 音訊要實際存 GCS(Google Cloud Storage),仍需 bucket、runtime service account 權限、部署 env,以及正式環境 API 狀態確認。

若要啟用 VoiceIT 音訊存 GCS(Google Cloud Storage),需要補齊

  • 建立或指定音訊 bucket。
  • euis-voiceitsv runtime service account 上傳物件權限,例如 bucket-level storage.objects.create
  • 若 admin 端要讀音檔 signed URL,需確保 service account 可產 V4 signed URL;Workload Identity 場景通常要確認 signBlob / Service Account Token Creator 等效能力。
  • 部署環境設定 OPENAI_REALTIME_AUDIO_STORAGE_MODE=gcsOPENAI_REALTIME_GCS_BUCKET=<bucket>,以及 OPENAI_REALTIME_GCS_PROJECT_IDGCP_PROJECT_ID
  • 用一通測試通話確認 realtime_connections.audio_path 寫入 gs://.../audio/*.wav;若模式為 none,則 audio_path 應維持空值。
項目 目前/建議型態 原因 / 條件
完整通話音訊 預設不保存;若啟用則為 GCS(Google Cloud Storage)object 或本機 WAV;DB 欄位為 nullable audio_path VARCHAR/TEXT 大型二進位不進 DB;依 audio-storage-mode 選擇 none/local/gcs。
啟用 GCS(Google Cloud Storage)必要設定 OPENAI_REALTIME_AUDIO_STORAGE_MODE=gcsOPENAI_REALTIME_GCS_BUCKET=<bucket>OPENAI_REALTIME_GCS_PROJECT_IDGCP_PROJECT_ID bucket 空值時不 fallback 本機,會視為 GCS(Google Cloud Storage)設定錯誤,避免默默塞爆本機硬碟。
GCP 資源 啟用 Cloud Storage API、建立 bucket、設定 service account IAM。 上傳需要 storage.objects.create;產 signed URL 需要可簽 V4 URL 的 credentials / signBlob 能力。
Realtime event PostgreSQL JSONB 保留 OpenAI Realtime protocol 結構,方便查詢與回放。
audio chunk / base64 寫入前改為 [AUDIO_BASE64_REDACTED] 降低敏感資料與 DB 容量風險。

5. Guardrail 實作現況

Guardrail 已在 euis-voiceitsv 接到 Realtime event pipeline,不是只有資料表。

檢查點 事件來源 處理方式
輸入端 transcript conversation.item.input_audio_transcription.completed transcriptOpenAiModerationService.checkGuardrail
輸入端 conversation item conversation.item.done 且 role=user 抽取 input_text / input_audio.transcript 後檢查。
輸出端 content part response.content_part.added 抽取 output_text / output_audio.transcript 後檢查。
輸出端 response done response.done 遍歷 response output content,再做輸出端檢查。
  • 命中後寫入 guardrail_events
  • response.cancel 給 OpenAI upstream。
  • 關閉 client WebSocket,close reason 為 Guardrail triggered
  • 關閉 upstream WebSocket。

6. 文字模式與語音模式的 LLM 鏈

目前前端同一個 Realtime client 同時支援麥克風語音與文字輸入,因此在同一通 WebSocket session 內,文字與語音共用同一條 OpenAI Realtime 鏈、同一組 instructions、tools、MCP/function call 與 guardrail pipeline。

語音輸入路徑

getUserMedia → resample 24kHz → PCM16 → base64 → input_audio_buffer.append

文字輸入路徑

conversation.item.create,content 為 input_text,接著送 response.create

7. Secret / API Key 上線檢查

開發者需確認:正式環境不得依賴 repo 內明文 secret、測試 secret、placeholder secret 或預設 fallback secret。所有 OpenAI/Gemini key、DB password、Redis password、JWT/HS256 secret、TOTP secret 應由 Secret Manager、K8s Secret 或 CI/CD 環境變數注入。

  • euis-voiceitsv 已看到 ${sm://...} fallback,例如 openai_api_key、DB password、BFF token secret。
  • BFF 專案也有 ${sm://voiceit_*_token_hs256_secret} 類設定,但仍需確認部署檔沒有硬編碼正式密鑰。
  • 建議 prod/stag 啟動時 fail fast:空值、placeholder、測試值、長度不符即拒絕啟動。
  • GCP 認證建議使用 Workload Identity,避免將 service account key 檔放入 repo 或 image。

8. BigQuery / Cloud SQL / Redis 結論

技術 目前查核結果 說明
BigQuery 未看到使用 四個主要專案未看到 BigQuery client、設定或查詢。
Cloud SQL 部署層可能使用 程式碼層是 jdbc:postgresql datasource。若環境變數 DB_URL 指向 Cloud SQL PostgreSQL,則持久化 DB 即為 Cloud SQL。
Redis BFF session 有設定;Realtime 狀態未使用 euis-voiceit-ui / euis-voiceitadmin-ui 有 Redis Session dependency/config;euis-voiceitsv Realtime call state 是 in-memory。
GCS(Google Cloud Storage) 程式支援,需環境啟用 OPENAI_REALTIME_AUDIO_STORAGE_MODE=gcs、bucket、IAM;預設 none 不保存音檔。

開發建議清單

新增 ConversationEnded 事件

RealtimeConnectionLifecycleManager.onConnectionClosed 完成所有收尾後發布 domain event 或寫 outbox。

補 close timeout / reconciler

避免 client 異常斷線、節點重啟或 close 流程中斷造成 IN_PROGRESS 卡住。

明確定義 memory pipeline

若未來要做長期記憶,需設計資料來源、摘要策略、PII 處理、TTL、刪除權、讀回時機與權限。

Guardrail 後台化

將分類規則、關鍵字、啟用狀態、事件查詢做成 admin-ui 功能,並保留調整 audit trail。

Secret fail-fast

prod/stag 啟動時檢查 key/secret 是否仍為 placeholder 或測試值,避免上線後才暴露風險。

事件寫入可靠性

若稽核資料不可遺失,將 saveEventAsync / recordAsync 改成具 retry 或 outbox 的可靠寫入。

主要程式碼定位

主題 檔案 / 類別
Realtime WebSocket bridge euis-voiceitsv/.../RealtimeBridgeWebSocketHandler.java
通話生命週期 RealtimeConnectionLifecycleManager.javaRealtimeConnectionServiceImpl.java
事件持久化 RealtimeEventServiceImpl.javaRealtimeEvent.java
音訊儲存 / GCS(Google Cloud Storage) AudioStorageService.javaGcsAudioServiceImpl.javaRealtimeAudioServiceImpl.java
MCP session / tool call ToolCallProxyServiceImpl.java
Guardrail RealtimeUpstreamEventProcessorImpl.javaOpenAiModerationServiceImpl.javaGuardrailEventServiceImpl.java
PII / audio base64 redaction EventDataPiiMasker.java025-redact-audio-base64-from-realtime-events.xml
BFF WebSocket proxy / auth WebSocketProxyHandler.javaWebSocketAuthInterceptor.java
前端 Realtime client useGPTRealtimeConsumer.tschatAssistant/index.vuechatAssistantLogic.ts