Files
tobiichiGPT/exchange.md
2026-02-01 08:15:45 +08:00

291 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 工程師交接說明
## 後臺方案更換建議Streamlit
若未來認為 Rocket.Chat 部署過重或整合不便,可轉用 **Streamlit** 開發自定義管理後台。此方案能針對 Open WebUI 的資料結構進行 100% 客製化。
### 核心優勢
1. **極簡開發**:純 Python 即可構建前端,約 200 行程式碼可完成完整後台。
2. **超輕量級**:單一容器(~100MB 記憶體),無需額外資料庫(直接讀取現有 Postgres
3. **完全客製**:可完美呈現「用戶 → 對話 → 訊息」的三層結構,不受限於聊天軟體的頻道邏輯。
4. **部署簡單**:標準 Docker image `python:3.11-slim` + `pip install streamlit`
### 實作架構
* **前端**Streamlit Web App (Port 8501)
* **後端邏輯**:直連 PostgreSQL `reply_queue` 表格
* **功能**:自動刷新、用戶篩選、歷史回覆查詢
### 程式碼範例 (admin.py)
```python
import streamlit as st
import psycopg2
import pandas as pd
# 自動刷新設定 (每 5 秒)
from streamlit_autorefresh import st_autorefresh
st_autorefresh(interval=5000, key="msg_refresh")
st.set_page_config(layout="wide", page_title="TobiichiGPT Admin")
# 1. 連接資料庫
conn = psycopg2.connect("postgresql://user:pass@postgres:5432/tobiichiGPT")
# 2. 側邊欄:用戶列表
st.sidebar.title("用戶列表")
users = pd.read_sql("SELECT DISTINCT user_id FROM reply_queue", conn)
selected_user = st.sidebar.radio("選擇用戶", users['user_id'])
# 3. 主畫面:顯示該用戶的對話
if selected_user:
st.header(f"用戶: {selected_user}")
# 撈取該用戶訊息
msgs = pd.read_sql(
f"SELECT * FROM reply_queue WHERE user_id='{selected_user}' ORDER BY created_at DESC",
conn
)
for _, row in msgs.iterrows():
with st.expander(f"對話 {row['chat_id']} ({row['status']})", expanded=True):
st.info(f"用戶: {row['user_message']}")
if row['status'] == 'pending':
with st.form(key=f"form_{row['id']}"):
reply = st.text_area("回覆內容")
if st.form_submit_button("送出"):
# 更新資料庫
cur = conn.cursor()
cur.execute(
"UPDATE reply_queue SET admin_reply=%s, status='replied' WHERE id=%s",
(reply, row['id'])
)
conn.commit()
st.success("已回覆")
st.rerun()
else:
st.success(f"管理員: {row['admin_reply']}")
```
### 部署配置 (Docker)
**Dockerfile**:
```dockerfile
FROM python:3.11-slim
WORKDIR /app
RUN pip install streamlit psycopg2-binary pandas streamlit-autorefresh
COPY admin.py .
CMD ["streamlit", "run", "admin.py", "--server.port=8501", "--server.address=0.0.0.0"]
```
**docker-compose.yml**:
```yaml
admin-ui:
build: ./admin-ui
ports: ["8501:8501"]
environment:
- DB_HOST=postgres
- DB_PASSWORD=${DB_PASSWORD}
networks:
- tobiichiGPT-network
```
### 與 Rocket.Chat/Chatwoot比較
| 特性 | Streamlit (自建) | Rocket.Chat | Chatwoot |
| :--- | :--- | :--- | :--- |
| **對應 Open WebUI 結構** | ⭐⭐⭐⭐⭐ (完全貼合) | ⭐⭐⭐ (需用頻道模擬) | ⭐ (結構扁平) |
| **即時性** | ⭐⭐ (輪詢刷新) | ⭐⭐⭐⭐⭐ (WebSocket) | ⭐⭐⭐⭐⭐ (WebSocket) |
| **手機 App** | ⭐ (網頁版) | ⭐⭐⭐⭐⭐ (原生 App) | ⭐⭐⭐⭐⭐ (原生 App) |
| **資源消耗** | 低 (~100MB) | 中 (~500MB) | 高 (~1GB) |
| **適用場景** | 單人/少數管理員,追求輕量與精準管理 | 多人團隊協作,需要 App 通知 | 專業客服團隊 |
### 建議切換時機
若遇到以下情況,建議切換至 Streamlit 方案:
1. Rocket.Chat 的頻道/執行緒管理變得混亂,難以追蹤用戶對話。
2. 伺服器資源不足,無法負擔 Rocket.Chat + MongoDB。
3. 需要針對特定業務邏輯(如:查看用戶餘額、審核特定關鍵字)進行客製化開發。
## Rocket.Chat 實作紀錄
### 實作日期
2026年2月1日
### 系統架構
```
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Open WebUI │────▶│ API 轉接層 │────▶│ Rocket.Chat │
│ (Port 10060) │ │ (Port 18000) │ │ (Port 13000) │
│ 用戶前端 │◀────│ FastAPI │◀────│ 管理員後台 │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│ │
▼ ▼
┌──────────────────┐ ┌─────────────────┐
│ PostgreSQL │ │ MongoDB │
│ (Port 5432) │ │ (Port 27017) │
└──────────────────┘ └─────────────────┘
```
### 工作流程
1. **用戶發送訊息** → Open WebUI 呼叫 `/v1/chat/completions`
2. **API 轉接層接收** → 提取用戶資訊 (user_name, chat_id)
3. **創建 Rocket.Chat 頻道** → 頻道名稱: `{用戶名}-{對話ID前8位}`
4. **發送用戶訊息** → 顯示格式: `💬 用戶名: 訊息內容`
5. **輪詢等待回覆** → 每 2 秒檢查是否有管理員新訊息
6. **回傳給用戶** → 以 OpenAI 格式回傳管理員的回覆
### 已完成的修改
#### 1. docker-compose.yml
**新增服務:**
- `mongo`: MongoDB 7.0 (Rocket.Chat 8.0.1 需要 7.0+)
- `mongo-init-replica`: 初始化 MongoDB Replica Set
- `rocketchat`: Rocket.Chat 8.0.1
**修改服務:**
- `openwebui`: 新增 `ENABLE_FORWARD_USER_INFO_HEADERS=True` 環境變數
**關鍵配置:**
```yaml
mongo:
image: mongo:7.0 # 從 5.0 升級,因為 Rocket.Chat 8.0.1 不支援 5.0
command: mongod --oplogSize 128 --replSet rs0
rocketchat:
image: registry.rocket.chat/rocketchat/rocket.chat:latest
ports:
- "13000:3000"
environment:
MONGO_URL: mongodb://mongo:27017/rocketchat?replicaSet=rs0
MONGO_OPLOG_URL: mongodb://mongo:27017/local?replicaSet=rs0
openwebui:
environment:
- ENABLE_FORWARD_USER_INFO_HEADERS=True # 轉發用戶資訊到 API
```
#### 2. api/server.py
**新增 imports**
```python
import json
import hashlib
```
**核心函數重寫:**
| 函數名稱 | 功能說明 |
|---------|---------|
| `rocketchat_login()` | 使用帳密登入 Rocket.Chat取得 Auth Token |
| `get_or_create_chat_channel()` | 為每個對話創建專屬頻道 (不使用 Thread) |
| `send_user_message()` | 發送用戶訊息到頻道,回傳訊息 ID |
| `wait_for_admin_reply()` | 輪詢等待管理員回覆,過濾機器人訊息 |
**Headers 提取邏輯:**
```python
# Open WebUI 轉發的 Headers
user_id = headers_dict.get("x-openwebui-user-id")
chat_id = headers_dict.get("x-openwebui-chat-id")
user_name = headers_dict.get("x-openwebui-user-name")
user_email = headers_dict.get("x-openwebui-user-email")
```
**系統任務過濾:**
```python
# 跳過 Open WebUI 的標題/標籤/後續問題生成請求
if user_message.strip().startswith("### Task:"):
return {"choices": [{"message": {"content": "{}"}}], ...}
```
### 遇到的問題與解決方案
#### 問題 1: MongoDB 版本不相容
- **錯誤**: `MongoServerSelectionError`
- **原因**: Rocket.Chat 8.0.1 需要 MongoDB 7.0+,但配置的是 5.0
- **解決**: 將 `mongo:5.0` 升級為 `mongo:7.0`
#### 問題 2: postMessage 返回 400 錯誤
- **錯誤**: `alias` 參數需要特殊權限
- **原因**: Rocket.Chat API 的 `alias` 欄位需要 bot 權限
- **解決**: 移除 `alias` 參數,改用訊息內文標註用戶名
#### 問題 3: Open WebUI 不發送用戶資訊 Headers
- **錯誤**: `user_name` 始終為 `None`
- **原因**: Open WebUI 預設不轉發用戶資訊
- **解決**: 設置環境變數 `ENABLE_FORWARD_USER_INFO_HEADERS=True`
#### 問題 4: 奇怪的系統訊息被發送到 Rocket.Chat
- **錯誤**: `### Task: Generate a concise title...` 等訊息出現
- **原因**: Open WebUI 會額外發送標題/標籤/後續問題生成請求
- **解決**: 過濾以 `### Task:` 開頭的訊息,直接回傳空 JSON
#### 問題 5: 重複回傳相同的管理員回覆
- **錯誤**: 每次用戶訊息都收到同一個回覆
- **原因**: Thread 架構複雜,難以正確追蹤新回覆
- **解決**: 改用簡化架構 (每個對話一個 Channel不用 Thread)
### 最終架構決策
**放棄 Thread 架構,改用 Channel 架構:**
| 項目 | Thread 架構 (放棄) | Channel 架構 (採用) |
|-----|-------------------|-------------------|
| 結構 | 用戶→頻道→多個Thread | 對話→專屬頻道 |
| 追蹤 | 需要追蹤 Thread ID | 只需追蹤訊息 ID |
| 複雜度 | 高 | 低 |
| 可讀性 | 中 (Thread 內對話) | 高 (直接看頻道) |
**頻道命名規則:**
```
{用戶名小寫}-{chat_id前8位}
例: ckliu-67b4341e
```
### 待辦事項
- [ ] 實作管理員回覆後的通知機制
- [ ] 處理超時後的重試邏輯
- [ ] 新增管理員身份驗證 (目前使用單一 admin 帳號)
- [ ] 考慮切換到 Streamlit 方案 (更輕量、完全客製化)
### 相關檔案
```
tobiichiGPT/
├── docker-compose.yml # 容器編排配置
├── api/
│ ├── server.py # API 轉接層主程式
│ └── requirements.txt # Python 依賴
└── exchange.md # 本文件
```
### 環境變數清單
| 變數名稱 | 用途 | 預設值 |
|---------|------|-------|
| `ROCKETCHAT_URL` | Rocket.Chat API 位址 | `http://rocketchat:3000` |
| `ROCKETCHAT_USER` | 登入帳號 | `admin` |
| `ROCKETCHAT_PASSWORD` | 登入密碼 | `admin` |
| `DB_HOST` | PostgreSQL 主機 | `postgres` |
| `DB_PASSWORD` | 資料庫密碼 | (必填) |
### 測試指令
```bash
# 重建並重啟 API 容器
cd /mnt/data/External/tobiichiGPT
sudo docker-compose up -d --build api
# 查看 API 日誌
sudo docker logs tobiichiGPT-api --tail 100
# 重啟 Open WebUI (套用 Headers 設定)
sudo docker-compose up -d openwebui
```