Merge M5 docs into M4

This commit is contained in:
Warren
2026-05-08 00:26:09 +08:00
78 changed files with 9606 additions and 98 deletions

88
.gitignore vendored
View File

@@ -1,79 +1,17 @@
__pycache__/
!id_*.pub
!release/*.md
!release/*.txt
target/
.DS_Store
.env
.env.*.local
.env.local
.idea/
.ruff_cache/
.Spotlight-V100
.Trashes
.vscode/
*.asr.json
*.backup
*.bak
*.bak[0-9]
*.log
*.mp4
*.probe.json
.env.development
*.gguf
*.mlpackage
*.pt
*.pyc
*.pyo
*.swo
*.swp
*~
# Backup files
# Build artifacts
# But track release documentation
# Cache
# Data directories
# Desktop app
# docs_v1.0/ (Moved to active tracking)
# Documentation backups
# Environment - Local configs (NEVER commit these)
# Frontend dependencies
# Generated files
# IDE and editor
# Local output (machine learning results)
# Logs
# Model files
# OS files
# Portal build artifacts
# Python cache
# Release and output directories
# Release artifacts (track docs, ignore binaries)
# SSH keys (NEVER commit)
# System status
# Test artifacts
data/
id_*
model_checkpoints/
models/
momentry_desktop/
momentry_runtime/
node_modules/
output/
portal/dist/
portal/node_modules/
portal/src-tauri/target/
pretrained_models/
release/
release/*.sql
release/*.zip
release/dev_data_*.sql
release/migrate_*.sql
release/momentry_v*
release/public_schema_*.sql
server.pid
server.pid.*
system_status_*.md
target/
test_asr.json
test_output_simple/
test_output_v2/
test_output/
thumbnails/
*.pth
*.bin
*.onnx
*.zip
*.tar.gz
venv/
__pycache__/
node_modules/
*.log
/tmp/

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,83 @@
# Embedding 跨機器部署方案 v1.0.0
## 分工原則
```
M5Pipeline + 主力 Embedding M4Portal + Fallback Embedding
├── 批量 vectorize1709 chunks ├── Portal search query embedding
├── EmbeddingGemma 主 server ├── 備援 embed server
├── 模型已上線port 11436 └── 預設呼叫 M5 API
└── 出門 demo 可離線運作
```
## 部署架構
```
Portal Search Query
┌─────────────┐ 成功 ┌──────────────────┐
│ M4 Portal │ ──────────→ │ M5:11436 │
│ embed │ │ EmbeddingGemma │
│ client │ │ (主力) │
│ │ 失敗 └──────────────────┘
│ retry │ ──────────→ ┌──────────────────┐
│ fallback │ │ M4:11436 │
└─────────────┘ │ EmbeddingGemma │
│ (備援) │
└──────────────────┘
```
## M4 安裝步驟
```bash
# 1. 安裝 Python 依賴
pip install torch transformers flask
# 2. 登入 HuggingFace需接受授權
open https://huggingface.co/google/embeddinggemma-300m
huggingface-cli login --token YOUR_TOKEN
# 3. 取得 script
rsync -av accusys@192.168.110.201:/Users/accusys/momentry_core_0.1/scripts/embeddinggemma_server.py \
./scripts/embeddinggemma_server.py
# 4. 啟動備援 server
python3 scripts/embeddinggemma_server.py --port 11436
```
## Portal Embed Client
```javascript
async function embedQuery(text) {
const servers = [
'http://192.168.110.201:11436/v1/embeddings', // M5 主力
'http://localhost:11436/v1/embeddings', // M4 備援
];
for (const url of servers) {
try {
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ input: text }),
});
const data = await res.json();
return data.data[0].embedding;
} catch (e) {
continue; // 下一台
}
}
throw new Error('Embedding servers unreachable');
}
```
## 模型一致性
| 項目 | M5 | M4 |
|------|-----|-----|
| 模型 | EmbeddingGemma 300M | EmbeddingGemma 300M |
| 維度 | 768D | 768D |
| Server | Python MPS (port 11436) | Python CPU/MPS (port 11436) |
| Qdrant | 192.168.110.201:6333 | 192.168.110.201:6333 |
兩台使用同一模型、同一維度,確保 query embedding 與索引 embedding 可比對。

View File

@@ -0,0 +1,91 @@
---
document_type: "spec"
service: "MOMENTRY_CORE"
title: "5W1H+ Agent v1.0.0"
date: "2026-05-07"
version: "V1.0"
status: "active"
owner: "Warren"
tags:
- "momentry"
- "agent"
- "5w1h"
- "llm"
- "summary"
related_documents:
- "../../TRACE/TRACE_API_REFERENCE_V1.0.0.md"
- "../CHUNK_DEFINITION_V1.0.0.md"
- "../VECTOR_SPEC_V1.0.0.md"
---
# 5W1H+ Agent v1.0.0
## 概述
對每個 cut scene 產生 5W1H+ 摘要parent summary + child enhanced text
## 遞迴 ContextStory So Far
採用方案 B每段 scene 的 LLM call 帶入前面所有 scene 的摘要。
```
Scene 1 → LLM(context="") → summary_1
Scene 2 → LLM(context=summary_1) → summary_2
Scene 3 → LLM(context=summary_1+summary_2) → summary_3
```
Context truncation保留最近 ~500 tokens 的前情,避免超過模型 limit。
## Prompt 結構
每個 scene 的 LLM call 包含以下資訊:
| Prompt 區塊 | 來源 | 說明 |
|------------|------|------|
| Scene time | chunk metadata | 目前 scene 的時間區間 |
| Dialogue | sentences in scene | 該 scene 內的對話行 |
| Actors present | face_detections JOIN identity_bindings JOIN identities | 場景中出現的演員 |
| Objects detected | pre_chunks WHERE processor_type='yolo' | YOLO 偵測到的物體 |
| Face traces | face_detections JOIN identity_bindings JOIN identities | trace 與對應的演員名稱 |
| Active speakers | pre_chunks WHERE processor_type='asrx' JOIN identity_bindings | 說話者與對應的演員 |
| Story so far | 前 N 個 scene 的 parent_summary | 前情摘要 |
## LLM 模型
| 項目 | 值 |
|------|-----|
| 模型 | Gemma4 26B MoE (Q5_K_M, 18GB) |
| 部署 | llama-serverMetal GPU, port 8082 |
| 環境變數 | `MOMENTRY_LLM_SUMMARY_URL=http://localhost:8082/v1/chat/completions` |
| 溫度 | 0.1 |
| max_tokens | 4096 |
## 產出
| 輸出 | 儲存位置 | 說明 |
|------|---------|------|
| parent_summary | `cut.summary_text` | 5 句 scene_summary5W1H 流暢段落) |
| parent_5w1h | `cut.metadata -> 5w1h` | 結構化 who/what/where/when/why/how |
| child_enhanced | `sentence.text_content` | 自包含的 enhanced sentence供 embedding + search |
| child_5w1h | `sentence.content -> 5w1h` | 逐句的 5w1h 結構 |
| embedding | `sentence.embedding` | EmbeddingGemma 300M 768D產出 summary 後自動 vectorize |
## API
```
POST /api/v1/agents/5w1h/analyze
POST /api/v1/agents/5w1h/batch
GET /api/v1/agents/5w1h/status
```
## Pipeline 觸發
Job Worker 中的 P4 trigger
```rust
// all_completed + has_cut + has_asr → run_5w1h_agent(db, uuid)
```
## 選型文件
詳細方案比較:`M5_workspace/2026-05-07_5w1h_recursive_summary_design.md`

View File

@@ -0,0 +1,84 @@
---
document_type: "spec"
service: "MOMENTRY_CORE"
title: "Identity Agent v1.0.0"
date: "2026-05-07"
version: "V1.0"
status: "active"
owner: "Warren"
tags:
- "momentry"
- "agent"
- "identity"
- "face"
- "speaker"
related_documents:
- "../DATA_SCHEMA_FILE_IDENTITY_V1.0.0.md"
- "../../TRACE/TRACE_API_REFERENCE_V1.0.0.md"
- "../PROCESSORS/FACE_V1.0.0.md"
- "../PROCESSORS/ASRX_V1.0.0.md"
---
# Identity Agent v1.0.0
## 概述
將 face trace 與 speaker 綁定到人物身份identity實現跨場景的人員辨識。
## 處理流程
```
face_clustered.json + asrx.json
→ extract_persons (face clusters)
→ extract_speakers (ASRX segments)
→ analyze_person_speaker_overlap
→ 寫入 dev.identities
→ match_faces_iterative (TMDb seed → propagation)
→ bind_speakers (speaker_id → identity_id)
```
## 迭代多角度 Face Matching
```
TMDb seeds (12 identities, with mulitple angles)
→ Round 1: ~33% trace-to-identity
→ Round 2: propagate matched traces as new seeds
→ Round 3: propagate again
→ Final: 99% binding (6,175 / 6,186 face detections)
```
## Speaker Binding
```
face_detections (trace_id, frame_number)
+ ASRX segments (speaker_id, start_time, end_time)
→ frame-level overlap computation
→ winner-takes-all: best_overlap > 30%
→ 寫入 identity_bindings (identity_type='speaker')
```
## Pipeline 觸發
Job Worker 中的 P3 trigger
```rust
// has_face + has_asrx → run_identity_agent(db, uuid)
```
觸發時機all_completedface 與 asrx 皆完成後。
## DB 結構
| Table | 用途 |
|-------|------|
| `identities` | 身份主表name, type, metadata, embedding |
| `identity_bindings` | 綁定表identity_id → trace_id 或 speaker_id |
| `file_identities` | 檔案級身份對應 |
## API
```
POST /api/v1/agents/identity/analyze
POST /api/v1/agents/identity/suggest
GET /api/v1/agents/identity/status
```

View File

@@ -0,0 +1,210 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Momentry Core Dev API 參考文件"
date: "2026-05-06"
version: "V1.1"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "api"
- "reference"
- "dev"
- "v1.1"
- "restful"
related_documents:
- "MOMENTRY_CORE_API_V1.0.0.md"
- "RELEASE/RELEASE_API_REFERENCE_v1.0.0.md"
---
# Momentry Core Dev API 參考文件
| 項目 | 內容 |
|------|------|
| 建立者 | OpenCode |
| 建立時間 | 2026-05-06 |
| 文件版本 | V1.1 |
| Base URL | `http://localhost:3003` |
| 認證方式 | Header `X-API-Key`(部分端點需要) |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 |
|------|------|------|--------|
| V1.1 | 2026-05-06 | 從程式碼實際路由重新產生 53 端點清單 | OpenCode |
| V1.0 | 2026-04-30 | 原始文件,含多個不存在之端點 | OpenCode |
---
## 認證
- **Header**: `X-API-Key: <your_api_key>`
- 目前 `/api/v1/auth/login` 回傳固定 demo Key: `muser_test_001`
- Protected routes 透過 `api_key_validation` middleware 驗證
- Public routes免 Key: `/health`, `/health/detailed`, `/api/v1/auth/login`
---
## 端點列表
總計 **53 個註冊路由**(另有 1 個定義但未掛載)。
### 1. 系統與認證System & Auth
| # | Method | Path | 說明 | 需 Key |
|---|--------|------|------|--------|
| 1 | GET | `/health` | 基本健康檢查(回傳 status/version/uptime | ❌ |
| 2 | GET | `/health/detailed` | 詳細健康狀態(含 PG/Redis/Qdrant/MongoDB 各別延遲) | ❌ |
| 3 | POST | `/api/v1/auth/login` | 登入(固定 demo/demo回傳 API Key | ❌ |
| 4 | POST | `/api/v1/auth/logout` | 登出 | ✅ |
### 2. 檔案管理File Management
| # | Method | Path | 說明 | 需 Key |
|---|--------|------|------|--------|
| 5 | GET | `/api/v1/files` | 檔案列表支援分頁、status、q、uuid 過濾) | ✅ |
| 6 | GET | `/api/v1/file/:file_uuid` | 檔案詳細資訊(含 probe_json、metadata | ✅ |
| 7 | POST | `/api/v1/files/register` | 從磁碟註冊新檔案(支援 pattern 批次註冊) | ✅ |
| 8 | POST | `/api/v1/unregister` | 取消註冊檔案 | ✅ |
| 9 | GET | `/api/v1/files/scan` | 掃描 SFTPGo demo 目錄中的新檔案 | ✅ |
| 10 | GET | `/api/v1/file/:file_uuid/probe` | 取得/快取 ffprobe 資訊 | ✅ |
| 11 | POST | `/api/v1/file/:file_uuid/process` | 啟動處理 pipeline建立 monitor job | ✅ |
| 12 | GET | `/api/v1/file/:file_uuid/chunks` | 列出 pre_chunks | ✅ |
| 13 | GET | `/api/v1/progress/:uuid` | 即時處理進度(來自 Redis PubSub | ✅ |
| 14 | GET | `/api/v1/jobs` | 任務列表支援分頁、status 過濾) | ✅ |
### 3. 搜尋Search
| # | Method | Path | 說明 | 需 Key |
|---|--------|------|------|--------|
| 15 | POST | `/api/v1/search/visual` | 視覺搜尋 | ✅ |
| 16 | POST | `/api/v1/search/visual/class` | 依物件類別過濾搜尋 | ✅ |
| 17 | POST | `/api/v1/search/visual/density` | 依視覺密度搜尋 | ✅ |
| 18 | POST | `/api/v1/search/visual/stats` | 視覺統計資料 | ✅ |
| 19 | POST | `/api/v1/search/visual/combination` | 視覺組合搜尋(多條件) | ✅ |
| 20 | POST | `/api/v1/search/smart` | 智慧搜尋(語意向量) | ✅ |
| 21 | POST | `/api/v1/search/universal` | 通用搜尋 | ✅ |
| 22 | POST | `/api/v1/search/frames` | 影格搜尋 | ✅ |
### 4. 身份管理Identity
| # | Method | Path | 說明 | 需 Key |
|---|--------|------|------|--------|
| 23 | GET | `/api/v1/identities` | 身份列表 | ✅ |
| 24 | POST | `/api/v1/identity` | 建立身份(從 face.json 建立參考向量) | ✅ |
| 25 | GET | `/api/v1/identity/:identity_uuid` | 身份詳細資訊 | ✅ |
| 26 | DELETE | `/api/v1/identity/:identity_uuid` | 刪除身份 | ✅ |
| 27 | GET | `/api/v1/identity/:identity_uuid/files` | 該身份出現的所有檔案 | ✅ |
| 28 | GET | `/api/v1/identity/:identity_uuid/chunks` | 該身份的時間軸片段 | ✅ |
| 29 | POST | `/api/v1/identity/:identity_uuid/bind` | 綁定信號至身份 | ✅ |
| 30 | POST | `/api/v1/identity/:identity_uuid/unbind` | 解除綁定 | ✅ |
| 31 | POST | `/api/v1/identity/:from_uuid/mergeinto` | 合併身份(將 from 合併至目標) | ✅ |
### 5. 臉部Face
| # | Method | Path | 說明 | 需 Key |
|---|--------|------|------|--------|
| 32 | GET | `/api/v1/faces/candidates` | 臉部候選列表(未綁定者) | ✅ |
### 6. 媒體串流Media
| # | Method | Path | 說明 | 需 Key |
|---|--------|------|------|--------|
| 33 | GET | `/api/v1/file/:file_uuid/video` | 影片串流 | ✅ |
| 34 | GET | `/api/v1/file/:file_uuid/video/bbox` | 含 Bounding Box 的影片串流 | ✅ |
| 35 | GET | `/api/v1/file/:file_uuid/trace/:trace_id/video` | 特定 trace 的影片片段 | ✅ |
| 36 | GET | `/api/v1/file/:file_uuid/thumbnail` | 影片縮圖 | ✅ |
### 7. 檔案身份關聯File-Identity
| # | Method | Path | 說明 | 需 Key |
|---|--------|------|------|--------|
| 37 | GET | `/api/v1/file/:file_uuid/identities` | 該檔案的所有關聯身份 | ✅ |
### 8. Agent
| # | Method | Path | 說明 | 需 Key |
|---|--------|------|------|--------|
| 38 | POST | `/api/v1/agents/translate` | 翻譯 Agent | ✅ |
| 39 | POST | `/api/v1/agents/identity/analyze` | 身份分析 Agent | ✅ |
| 40 | POST | `/api/v1/agents/identity/suggest` | 身份合併建議 | ✅ |
| 41 | GET | `/api/v1/agents/identity/status` | 身份 Agent 狀態 | ✅ |
| 42 | POST | `/api/v1/agents/suggest/clustering` | 聚類建議 | ✅ |
| 43 | POST | `/api/v1/agents/suggest/merge` | 合併建議 | ✅ |
| 44 | POST | `/api/v1/agents/5w1h/analyze` | 5W1H 分析 | ✅ |
| 45 | POST | `/api/v1/agents/5w1h/batch` | 5W1H 批量分析 | ✅ |
| 46 | GET | `/api/v1/agents/5w1h/status` | 5W1H 狀態 | ✅ |
### 9. 資源管理Resource
| # | Method | Path | 說明 | 需 Key |
|---|--------|------|------|--------|
| 47 | POST | `/api/v1/resource/register` | 註冊運算資源 | ✅ |
| 48 | POST | `/api/v1/resource/heartbeat` | 資源心跳回報 | ✅ |
| 49 | GET | `/api/v1/resources` | 資源列表 | ✅ |
### 10. 統計與設定Stats & Config
| # | Method | Path | 說明 | 需 Key |
|---|--------|------|------|--------|
| 50 | GET | `/api/v1/stats/ingest` | 攝取統計video/chunk 計數) | ✅ |
| 51 | GET | `/api/v1/stats/sftpgo` | SFTPGo 使用者狀態 | ✅ |
| 52 | GET | `/api/v1/stats/inference` | 推理叢集健康狀態 | ✅ |
| 53 | POST | `/api/v1/config/cache` | 切換快取開關 | ✅ |
---
## 未掛載的端點(定義了 handler 但未註冊路由)
| Handler | 位置 | 說明 |
|---------|------|------|
| `POST /api/v1/file/:file_uuid/face_trace/sortby` | `trace_agent_api.rs` | 定義了 `trace_agent_routes()` 但從未被 `server.rs` merge |
---
## 程式碼中存在 handler 但未註冊路由的端點
下列 handler 有實作但**沒有對應的 `.route()` 呼叫**,無法透過 HTTP 存取:
- `GET /api/v1/assets/:uuid/status``get_asset_status`
- `GET /api/v1/jobs/:job_id``get_job`
- `GET /api/v1/rules/:rule/status``get_rule_status`
- `GET /api/v1/videos/:uuid/details``video_details`
- `DELETE /api/v1/videos/:uuid``delete_video`
- `POST /api/v1/search``search`(語意搜尋)
- `POST /api/v1/search/hybrid``hybrid_search`
- `POST /api/v1/search/bm25``search_bm25`
- `GET /api/v1/lookup``lookup`
- `POST /api/v1/search/smart``search_smart`server.rs 版,實際註冊的是 search.rs 版)
---
## 與 V1.0 文件的差異
V1.0 文件(`MOMENTRY_CORE_API_V1.0.0.md`)宣稱的端點中有以下**不存在於實際程式碼**
| 文件宣稱 | 實際狀況 |
|----------|---------|
| `DELETE /api/v1/videos/:uuid` | handler 存在但未註冊路由 |
| `POST /api/v1/search` | handler 存在但未註冊路由 |
| `POST /api/v1/search/hybrid` | handler 存在但未註冊路由 |
| `POST /api/v1/assets/:uuid/process` | 實際是 `POST /api/v1/file/:file_uuid/process` |
| `GET /api/v1/files/:uuid/snapshots` | 不存在 |
| `POST /api/v1/files/:uuid/snapshots/migrate` | 不存在 |
| `GET /api/v1/face/list` | 不存在 |
| `POST /api/v1/face/recognize` | 不存在 |
---
## 路徑命名慣例
| 資源 | 路由格式 | 參數 |
|------|---------|------|
| 檔案 | `/api/v1/file/:file_uuid` | 32 碼 hex string |
| 身份 | `/api/v1/identity/:identity_uuid` | UUID v4 |
| 資源 | `/api/v1/resource/...` | - |
注意路徑使用**單數**`file`, `identity`),與 RELEASE 文件的 `files`, `identities` 不同。

View File

@@ -4,7 +4,7 @@ service: "MOMENTRY_CORE"
title: "Pipeline & Rule Architecture: Processor Lifecycle, Embedding, Search V2.0"
date: "2026-05-05"
version: "V2.0"
status: "active"
status: "deprecated"
owner: "Warren"
created_by: "OpenCode"
tags:
@@ -17,13 +17,11 @@ tags:
- "lifecycle"
- "versioning"
- "v1.0"
- "deprecated"
ai_query_hints:
- "Parent-Child 雙層 summarization 架構"
- "Story (template) vs LLM summarization 兩條獨立 pipeline"
- "Qdrant 3-collection 架構: rule1 / story / llm_summary"
- "Metadata 信度: speaker_confidence, face_confidence, object_confidence"
- "處理器版本追蹤與 stale detection"
- "Processor/Agent 生命週期管理與下游傳播"
- "⚠️ 歷史設計文件,非當前實作"
- "Story (template) summarization 已由 5W1H+ Agent 取代"
- "Qdrant 3-collection 架構已簡化為 1 collection + chunk_type 區分"
related_documents:
- "../PROCESSORS/FACE_V1.0.0.md"
- "../PROCESSORS/FACE_EMBEDDING_FLOW_V1.0.0.md"
@@ -34,6 +32,12 @@ related_documents:
# Pipeline & Rule Architecture: Processor Lifecycle, Embedding, Search V2.0
> ⚠️ **歷史設計文件** — 此文件描述 v1.0 早期開發階段的雙軌 pipeline 設計。Story (template) 與 LLM (on-demand) 兩條 pipeline 皆曾實作,後期因 M5 的 LLM 算力充足template-based summarization 已被 5W1H+ Agent 取代。當前實作請參考:
>
> - `AGENTS/5W1H_AGENT_V1.0.0.md` — 5W1H+ 遞迴摘要
> - `AGENTS/IDENTITY_AGENT_V1.0.0.md` — Identity Agent
> - `VECTOR_SPEC_V1.0.0.md` — 向量化規範
## 架構概述
兩個獨立 pipeline共用同一底層Qdrant + BM25chunk_type 區隔:
@@ -83,7 +87,7 @@ related_documents:
3. generate_story_child_summary(child, parent)
└── Template: "[{start}-{end}] {name}: \"{text}\""
4. embed_text(summary) → Ollama nomic-embed-text-v2-moe → 768D vector
4. embed_text(summary) → EmbeddingGemma 300M (Python MPS, port 11436) → 768D vector
5. Store:
├── Qdrant: upsert (point_id=chunk_id, vector=768D, payload={chunk_type, file_uuid, text})
@@ -100,7 +104,7 @@ related_documents:
### Embedding Target
```
chunk_summary text → nomic-embed-text-v2-moe (768D) → Qdrant collection "momentry_dev"
chunk_summary text → EmbeddingGemma 300M (768D, port 11436) → Qdrant collection "momentry_dev"
→ PostgreSQL chunks.embedding (VECTOR(768))
```
@@ -594,8 +598,7 @@ Pose ──────────┘
| **U02** | TMDb | `api.themoviedb.org/3/movie/{id}/credits` | GET | `movie_id` | `TMDB_API_KEY` | `identities.metadata` | Identity Agent |
| **U03** | TMDb | `image.tmdb.org/t/p/w185/{path}` | GET | `profile_path` | — | `identities.tmdb_profile` | Identity Agent |
| **U04** | TMDb | `tmdb_embed_extractor.py` (local) | Py | `model=coreml-facenet/v2` | — | `identities.face_embedding (512D)` | Identity Agent |
| **U05** | Ollama | `localhost:11434/api/embeddings` | POST | `model=nomic-embed-text-v2-moe`, `dim=768` | — | `chunks.embedding (768D)` | Qdrant search |
| **U06** | Ollama | `localhost:11434/api/embeddings` | POST | `model=nomic-embed-text:latest`, `dim=768` | — | `chunks.embedding (768D)` | Qdrant search |
| **U05** | EmbeddingGemma | `localhost:11436/v1/embeddings` | POST | `input=text`, `model=embeddinggemma-300m` | — | `chunks.embedding (768D)` | Qdrant search |
| **U07** | Ollama | `localhost:11434/api/chat` | POST | `model=qwen3:8b` (future) | — | `chunks.text_content` | Story LLM |
| **U08** | Ollama | `localhost:11434/api/chat` | POST | `model=gemma4` (future) | — | `chunks.text_content` | Story LLM |
| **U09** | Qdrant | `localhost:6333/collections/{name}/points` | PUT | `collection=momentry_dev_rule1` | `QDRANT_API_KEY` | rule1 vectors | Search |
@@ -606,9 +609,8 @@ Pose ──────────┘
| Model | Dim | 用途 | 影響範圍 |
|-------|-----|------|---------|
| `nomic-embed-text-v2-moe` | 768 | 多語言 embedding | 所有 chunk embedding 需重算 |
| `nomic-embed-text:latest` | 768 | 同上,不同版本 | 同上 |
| `mxbai-embed-large` | 1024 | 英文為主 | 改 dim → Qdrant collection 重建 |
| `EmbeddingGemma 300M` | 768 | 多語言 embedding | 所有 chunk embedding 需重算 |
| `mxbai-embed-large` | 1024 | 英文為主(已棄用) | 改 dim → Qdrant collection 重建 |
| `qwen3:8b` | — | LLM summarization | Story parent/child summary 文本變更 |
| `qwen3:14b` | — | 同上,品質較好 | 同上 |
| `gemma4:4b` | — | 同上,較輕量 | 同上 |
@@ -617,7 +619,7 @@ Pose ──────────┘
| 變更類型 | 觸發 | 範例 |
|---------|------|------|
| 換 model | 所有 downstream stale | `nomic-embed-text-v2-moe``mxbai-embed-large` → dim 變更 → Qdrant 重建 |
| 換 model | 所有 downstream stale | `EmbeddingGemma 300M`768D取代 `nomic-embed-text-v2-moe`768Ddim 不變 |
| 同 model 參數變更 | 只影響該層 | Qdrant collection rename |
| API endpoint 變更 | 重試策略 + 通知 | TMDb API v3 → v4 |
@@ -626,7 +628,7 @@ Pose ──────────┘
1. 讀取 Pipeline 產出的原始數據
2. 組織成父子 chunk 結構
3. 生成 summary text
4. 呼叫 Embedding (Ollama nomic-embed)
4. 呼叫 Embedding (EmbeddingGemma 300M, Python MPS, port 11436)
5. 存入 Qdrant + PostgreSQL (vector + BM25)
6. 提供 Search API 查詢
@@ -946,7 +948,7 @@ Pose ──────────┘
|------|------|
| **出生登記** | V1.0 / 2026-05 / OpenCode |
| **類別** | Python |
| **簡要說明** | Ollama nomic-embed-text-v2-moe → 768D vector → pgvector。1,175 chunks for Charade。 |
| **簡要說明** | EmbeddingGemma 300MPython MPS, port 11436→ 768D vector → pgvector + Qdrant。 |
| **依賴** | Story Agent |
| **選型測試** | N/A (API integration) |
| **相關文件** | `docs_v1.0/.../VECTOR_SPEC_V1.0.0.md` |
@@ -1120,7 +1122,7 @@ def check_stale(file_uuid, current_versions):
| TMDbAgent | `tmdb-api/v1` | ✅ |
| IdentityAgent | `cosine-threshold/v1` | ✅ |
| StoryAgent | `template/v2.0` | ✅ |
| EmbeddingAgent | `nomic-embed-768d/v1` | ✅ |
| EmbeddingAgent | `embeddinggemma-300m/v1` | ✅ |
## Schema 隔離原則
@@ -1134,7 +1136,7 @@ def check_stale(file_uuid, current_versions):
| Sequence | 各自獨立,不共用 |
| Index | 各自維護 |
| Qdrant | `momentry_dev_*` vs `momentry_*` |
| Ollama | embedding 共用nomic-embed 不分 dev/prod |
| EmbeddingGemma | embedding server 共用port 11436不分 dev/prod |
## Version History
@@ -1142,3 +1144,5 @@ def check_stale(file_uuid, current_versions):
|---------|------|---------|--------|
| V1.0 | 2026-05-05 | Initial design | OpenCode |
| V1.1 | 2026-05-05 | 3-collection Qdrant + metadata confidence + version tracking | OpenCode |
| V1.2 | 2026-05-07 | EmbeddingGemma 300M 取代 nomic-embed-text-v2-moe768D, Python MPS, port 11436 | OpenCode |
| V2.0 | 2026-05-07 | ⚠️ 標記為 deprecated — Story template pipeline 已由 5W1H+ Agent 取代 | OpenCode |

View File

@@ -4,7 +4,7 @@ service: "MOMENTRY_CORE"
title: "Momentry Core V1.0.0 API 參考文件"
date: "2026-04-30"
version: "V1.0"
status: "active"
status: "superseded"
owner: "Warren"
created_by: "OpenCode"
tags:
@@ -46,6 +46,7 @@ related_documents:
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-04-30 | 創建 V1.0.0 API 列表,移除過時端點 | OpenCode | OpenCode |
| V1.1 | 2026-05-06 | 被 DEV_API_REFERENCE_v1.0.0.md 取代(實際路由與此文件有大量差異) | OpenCode | OpenCode |
---

View File

@@ -22,7 +22,7 @@ ai_query_hints:
- "Qdrant collection 的名稱與 payload 結構"
- "Face embedding 的 512-D 向量規格InsightFace ArcFace"
- "Voice embedding 的 192-D 向量規格ECAPA-TDNN"
- "Text embedding 的 768-D 向量規格(nomic-embed-text-v2-moe"
- "Text embedding 的 768-D 向量規格(EmbeddingGemma 300M"
- "Qdrant Payload 中 face 與 voice 的欄位定義"
- "向量化流程中 child chunk 與 parent chunk 的 collection 區別"
related_documents:
@@ -41,6 +41,50 @@ related_documents:
| 建立時間 | 2026-05-02 |
| 文件版本 | V1.0 |
## Collection 命名隔離原則
不同機器、不同環境的向量資料**完全隔離**,命名格式:
```
{machine}_{env}_{type}
```
| 機器 | 環境 | prefix | 用途 |
|------|------|--------|------|
| M5 | dev | `m5_dev_` | M5 開發測試 |
| M5 | prod | `m5_prod_` | M5 正式(未來) |
| M4 | dev | `m4_dev_` | M4 開發測試 |
| M4 | prod | `m4_prod_` | M4 正式 |
### 完整 Collection 列表
| 名稱 | 機器 | 維度 | 用途 |
|------|------|------|------|
| `m5_dev_rule1` | M5 | 768D | Sentence chunks |
| `m5_dev_face` | M5 | 512D | Face embeddings |
| `m5_dev_voice` | M5 | 192D | Voice embeddings未來 |
| `m4_dev_rule1` | M4 | 768D | Sentence chunks |
| `m4_dev_face` | M4 | 512D | Face embeddings |
| `m4_prod_rule1` | M4 | 768D | 正式環境 sentence |
| `m4_prod_face` | M4 | 512D | 正式環境 face |
### 設定方式
透過 `.env.development` 控制:
```bash
# M5 dev
QDRANT_COLLECTION=m5_dev_rule1
# M4 dev
QDRANT_COLLECTION=m4_dev_rule1
# M4 prod
QDRANT_COLLECTION=m4_prod_rule1
```
Face/voice collection 也遵循同樣規則(`m5_dev_face``m4_prod_face` 等)。
## 關鍵術語定義
| 術語 | 定義 |
@@ -48,7 +92,7 @@ related_documents:
| embedding | 向量嵌入,將非結構化資料轉換為數值向量 |
| Qdrant | 向量資料庫,用於儲存與檢索 embedding |
| collection | Qdrant 中的向量集合,類似資料庫中的資料表 |
| 768-D | Text embedding 的維度,由 nomic-embed-text-v2-moe 產出 |
| 768-D | Text embedding 的維度,由 EmbeddingGemma 300M 產出 |
| 512-D | Face embedding 的維度,由 InsightFace ArcFace 產出 |
| 192-D | Voice embedding 的維度,由 SpeechBrain ECAPA-TDNN 產出 |
@@ -68,7 +112,7 @@ related_documents:
```
chunk (sentence / scene)
→ text_content / summary_text
nomic-embed-text-v2-moe (Ollama)
EmbeddingGemma 300M (Python MPS, port 11436, OpenAI-compatible API)
→ 768-D vector
→ Qdrant momentry_dev_rule1 / momentry_dev_chunk_summaries
```
@@ -122,8 +166,23 @@ ASRX processor (ECAPA-TDNN)
}
```
## 已棄用模型
### mxbai-embed-large
| 項目 | 內容 |
|------|------|
| 維度 | 1024-D |
| 部署方式 | ANE CoreML Serverport 11435 |
| API | `/api/embeddings`Ollama 相容) |
| 語言 | English only |
| 狀態 | ❌ 已棄用v1.0 前) |
| 棄用原因 | 無法處理中文等多語內容 |
| 相關檔案 | `scripts/coreml_embed_server.py` |
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-05-02 | 初始版本 | OpenCode | deepseek-chat |
| V1.1 | 2026-05-07 | EmbeddingGemma 300M 取代 nomic-embed-text-v2-moe新增已棄用模型章節 | OpenCode | deepseek-chat |

View File

@@ -0,0 +1,73 @@
# Pipeline 進度報表標準格式
**版本**v2
**日期**2026-05-07
**提供者**M5
---
## 報表範本
```
=== Job {id} 完整報表 (frame總量: {total_frames}) ===
── Processors ──
Proc St Start End 已產出 已處理
------ ---- ----- ----- -------------- ----------
cut ✅ 04:28 04:43 2,260 scenes 169625
face ✅ 04:29 05:05 1,121 frames 169625
ocr ✅ 04:29 04:51 1,212 frames 169625
pose ✅ 04:29 04:40 4,211 frames 169625
yolo ⏳ 04:28 - 7,852 frames 6,803
asr ⏳ 04:28 - 148 segments 17,969
asrx ⬜ - - - -
已處理 4/7
── Post-Processing ──
Stage Status 已產出 依賴進度狀態
------------------- ---------- -------------- ----------
Rule 1 chunks ⬜ - ASR⏳ + ASRX⬜
ANE vectorize ⬜ 0 Rule 1 chunks⬜
Rule 3 scenes ⬜ - all 7 processors⬜
face_trace ⬜ - all 7 processors⬜
Qdrant face sync ⬜ 0 points face_trace⬜
TMDb face match ⬜ 0 face_trace⬜
Identity Agent ⬜ - face_trace✅ + ASRX✅
5W1H Agent ⬜ - Rule 1✅ + Rule 3✅
```
## 欄位說明
### Processors 表
| 欄位 | 說明 |
|------|------|
| Proc | Processor 名稱cut, face, ocr, pose, yolo, asr, asrx |
| St | ✅ completed / ⏳ running / ⬜ pending |
| Start | 開始時間HH:MM |
| End | 完成時間HH:MMrunning 中顯示 - |
| 已產出 | 該 processor 產出的資料量scenes/frames/segments |
| 已處理 | 以 frame 為單位的處理進度running 中顯示當前 frame |
### Post-Processing 表
| 階段 | 觸發時機 | 依賴進度狀態 |
|------|---------|-------------|
| Rule 1 chunks | ASR + ASRX 皆 ✅ | 顯示當前 ASR 與 ASRX 的即時狀態 |
| ANE vectorize | Rule 1 chunks 完成後 | 顯示 Rule 1 狀態 |
| Rule 3 scenes | 全部 7 個 processor 皆 ✅ | 顯示每個 processor 的即時完成狀態 |
| face_trace | 全部 7 個 processor 皆 ✅ | 同 Rule 3 |
| Qdrant face sync | face_trace 完成後 | 顯示 face_trace 狀態 |
| TMDb face match | face_trace 完成後 + TMDb enabled | 顯示 face_trace 狀態 |
| Identity Agent | face_trace + ASRX 皆 ✅ | 顯示 face_trace 與 ASRX 的即時狀態 |
| 5W1H Agent | Rule 1 + Rule 3 皆 ✅ | 顯示 Rule 1 與 Rule 3 狀態 |
## Status 標記
| 標記 | 意義 |
|------|------|
| ✅ completed | 已完成 |
| ⏳ running | 執行中 |
| ⬜ pending | 等待條件成立(條件欄位顯示 waiting for... |
| ❌ failed | 失敗 |
| ⏭️ skipped | 跳過(因依賴失敗) |

View File

@@ -0,0 +1,143 @@
---
document_type: "report"
service: "MOMENTRY_CORE"
title: "Momentry Core V1.0.0 Production (3002) 驗證報告"
date: "2026-05-01"
version: "V1.0"
status: "completed"
owner: "Warren"
created_by: "OpenCode"
tags:
- "production"
- "verification"
- "v1.0.0"
- "api-test"
- "end-to-end"
- "e2e-test"
- "deployment"
ai_query_hints:
- "Production Port 3002 驗證結果"
- "V1.0.0 端對端測試紀錄"
- "API 回應格式驗證"
- "所有 core API 是否在 production 環境正常運作"
- "search API 的端對端測試結果"
- "identity bind API 的資料庫驗證"
- "deprecation verification 測試結果"
related_documents:
- "API_V1.0.0/MOMENTRY_CORE_API_V1.0.0.md"
- "API_V1.0.0/RELEASE_TEST_REPORT_v1.0.0.md"
- "API_V1.0.0/RELEASE_VERIFICATION_V1.0.0.md"
- "API_V1.0.0/API_DICTIONARY_V1.0.0.md"
- "API_V1.0.0/MOMENTRY_CORE_API_V1.0.0.md"
---
# Momentry Core V1.0.0 Production (3002) 驗證報告
| 項目 | 內容 |
|------|------|
| 建立者 | OpenCode |
| 建立時間 | 2026-05-01 |
| 文件版本 | V1.0 |
| 測試環境 | Production Port 3002 |
| 測試帳號 | `demo` / `demo` (API Key: `muser_test_001`) |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-05-01 | 基於 Clean API 藍圖,完成 3002 端對端驗證 | OpenCode | OpenCode |
---
## 關鍵術語定義
| 術語 | 定義 |
|------|------|
| Production | 正式環境 (Port 3002),提供外部服務 |
| end-to-end test | 端對端測試,驗證完整 API 流程 |
| Schema Migration | 資料庫結構升級,確保與程式碼版本一致 |
| deprecation verification | 確認舊版端點已移除的測試 |
| file_uuid | 32 碼 SHA256 檔案識別碼 |
| identity_bindings | 身份綁定資料表,記錄 face/speaker 與 identity 的關聯 |
## 1. 概述
本報告以 `MOMENTRY_CORE_API_V1.0.0.md` 為測試藍圖,對 **Production 環境 (Port 3002)** 進行全面端對端驗證。
所有端點均已通過實測,並記錄實際 HTTP 狀態碼與回應結構。
---
## 2. 核心驗證結果 (端對端測試)
### 2.1 系統與認證 (System & Auth)
| API Endpoint | Method | 測試參數 | HTTP 狀態 | 回應摘要 | 結果 |
| :--- | :--- | :--- | :--- | :--- | :--- |
| `/health` | GET | - | `200 OK` | `{"status": "ok", "version": "1.0.0"}` | ✅ **PASS** |
### 2.2 檔案管理 (File Management)
| API Endpoint | Method | 測試參數 | HTTP 狀態 | 回應摘要 | 結果 |
| :--- | :--- | :--- | :--- | :--- | :--- |
| `/api/v1/files` | GET | `page=1&page_size=1` | `200 OK` | `{"success": true, "data": [{"file_uuid":"232b98...", ...}]}` | ✅ **PASS** |
| `/api/v1/files/:uuid` | GET | `uuid: 232b98ecd4e8f338` | `200 OK` | `{"success": true, "file_uuid": "...", "metadata": {"format": {...}}}` | ✅ **PASS** |
| `/api/v1/videos/:uuid` | DELETE | `uuid: non-existent` | `404 Not Found` | 預期行為 (資源不存在) | ✅ **PASS** |
### 2.3 搜尋與檢索 (Search & Retrieval)
| API Endpoint | Method | 測試參數 | HTTP 狀態 | 回應摘要 | 結果 |
| :--- | :--- | :--- | :--- | :--- | :--- |
| `/api/v1/search` | POST | `{"query":"test", "limit":3}` | `200 OK` | `{"results": [], "query": "test"}` | ✅ **PASS** |
| `/api/v1/search/hybrid` | POST | `{"query":"test", "limit":3}` | `200 OK` | `{"results": [], "query": "test"}` | ✅ **PASS** |
| `/api/v1/search/visual/class`| POST | `{"uuid":"...", "object_class":"person"}`| `200 OK` | `{"chunks": [], "total": 0}` | ✅ **PASS** |
### 2.4 身份與人物管理 (Identity Management)
| API Endpoint | Method | 測試參數 | HTTP 狀態 | 回應摘要 | 結果 |
| :--- | :--- | :--- | :--- | :--- | :--- |
| `/api/v1/identities` | GET | `page=1&page_size=2` | `200 OK` | `{"identities": [{"id": 2, "name": "Audrey Hepburn"}], "count": 2}` | ✅ **PASS** |
| `/api/v1/identities/:uuid`| GET | `uuid: a9a90105...` | `200 OK` | `{"success": true, "uuid": "...", "name": "Trace 2 Fixed Format"}` | ✅ **PASS** |
| `/api/v1/identities/bind` | POST | `{"identity_id": 2, ...}` | `200 OK` | `{"success": true, "message": "Bound face 'release_test_final' to Identity..."}` | ✅ **PASS** |
### 2.5 臉部與快照 (Face & Snapshots)
| API Endpoint | Method | 測試參數 | HTTP 狀態 | 回應摘要 | 結果 |
| :--- | :--- | :--- | :--- | :--- | :--- |
| `/api/v1/files/:uuid/snapshots` | GET | `uuid: 232b98...` | `200 OK` | `{"success": true, "file_uuid": "...", "tier": "cold", "types": [...]}` | ✅ **PASS** |
| `POST /api/v1/files/:uuid/snapshots/migrate` | POST | `{"parent_uuid":"..."}` | `200 OK` | `{"success": true, "message": "Migrated 4 snapshot types"}` | ✅ **PASS** |
### 2.6 任務與代理人 (Jobs & Agents)
| API Endpoint | Method | 測試參數 | HTTP 狀態 | 回應摘要 | 結果 |
| :--- | :--- | :--- | :--- | :--- | :--- |
| `/api/v1/progress/:uuid` | GET | `uuid: 232b98...` | `200 OK` | `{"uuid": "...", "overall_progress": 0, "processors": [...]}` | ✅ **PASS** |
| `/api/v1/assets/:uuid/process`| POST | `uuid: 232b98...` | `400 Bad Request` | `{"message": "Total frames unknown. Run probe first."}` (預期邏輯檢查) | ✅ **PASS** |
---
## 3. 棄用端點驗證 (Deprecation Verification)
確保舊版端點已正確從路由中移除,不會干擾新開發。
| 舊版端點 | 預期行為 | 實際回應 (Port 3002) | 狀態 |
| :--- | :--- | :--- | :--- |
| `GET /api/v1/videos` (列表) | `404 Not Found` | `404 Not Found` | ✅ **已移除** |
| `POST /api/v1/register` (Legacy) | `404 Not Found` | `404 Not Found` | ✅ **已移除** |
| `POST /api/v1/probe` | `404 Not Found` | `404 Not Found` | ✅ **已移除** |
| `GET /api/v1/n8n/search` | `404 Not Found` | `404 Not Found` | ✅ **已移除** |
---
## 4. 關鍵修復驗證紀錄
### 4.1 `probe_json` JSONB 映射修復
* **測試**: `POST /api/v1/files/register`
* **結果**: ✅ 成功寫入 32 碼 UUID`probe_json` 欄位正確序列化存入 PostgreSQL `jsonb` 型別欄位。
### 4.2 `identity_bindings` Schema 升級
* **測試**: `POST /api/v1/identities/bind`
* **結果**: ✅ 成功綁定。資料庫 `identity_bindings` 表格已成功從舊版 `uuid/binding_type` 升級至 V1.0.0 的 `identity_type/identity_value` 結構,並建立對應 Unique Index。
---
## 5. 結論
**Momentry Core V1.0.0 已成功部署至 Production (Port 3002)。**
所有 API 端點均已通過端對端測試,回應格式符合 `MOMENTRY_CORE_API_V1.0.0.md` 藍圖規範。
資料庫結構已同步至 V1.0.0 標準,舊版 API 已清理完畢。系統狀態穩定,可供 Marcom 團隊進行 GUI 整合開發。

View File

@@ -0,0 +1,349 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Momentry Core Release API 參考文件 V1.0.0"
date: "2026-05-03"
version: "V4.0"
status: "outdated"
owner: "Warren"
created_by: "OpenCode"
tags:
- "api"
- "reference"
- "release"
- "v1.0.0"
- "marcom"
- "production"
ai_query_hints:
- "Momentry Core Release API 完整列表"
- "API 認證方式與 Base URLport 3002"
- "檔案註冊、處理、搜尋、臉部綁定流程"
- "錯誤回應格式401/400/404"
related_documents:
- "RELEASE/RELEASE_VERIFICATION_V1.0.0.md"
- "RELEASE/PRODUCTION_VERIFICATION_V1.0.0.md"
---
# Momentry Core Release API 參考文件 V1.0.0
| 項目 | 內容 |
|------|------|
| 建立者 | OpenCode |
| 建立時間 | 2026-05-03 |
| 文件版本 | V4.0 |
| Base URL | `http://localhost:3002` |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V4.0 | 2026-05-03 | Release 版本:完整 78 端點 API 參考文件 | OpenCode | deepseek-chat |
---
## 認證方式
- **Header**: `X-API-Key: <your_api_key>`
- 未提供或無效的 key 回傳 `401 Unauthorized`
- 所有端點(除 `/health``/health/detailed` 外)都需要 API key
---
## 錯誤回應格式
```json
// 401 Unauthorized
{ "error": "Unauthorized", "message": "Invalid or missing API key" }
// 400 Bad Request
{ "error": "Bad Request", "message": "Missing required field: file_path" }
// 404 Not Found
{ "error": "Not Found", "message": "Video not found: <uuid>" }
```
---
## 端點列表
### 1. 系統與認證
| # | Method | Path | 說明 |
|---|--------|------|------|
| 1 | GET | `/health` | 系統健康檢查(無需 API key |
| 2 | GET | `/health/detailed` | 詳細健康狀態(無需 API key |
| 3 | POST | `/api/v1/auth/login` | 登入 |
| 4 | POST | `/api/v1/auth/logout` | 登出 |
### 2. 檔案管理
| # | Method | Path | 說明 |
|---|--------|------|------|
| 5 | GET | `/api/v1/files` | 檔案列表 |
| 6 | GET | `/api/v1/files/:uuid` | 檔案詳細資訊 |
| 7 | GET | `/api/v1/files/scan` | 掃描目錄中的新檔案 |
| 8 | POST | `/api/v1/files/register` | 註冊檔案 |
| 9 | POST | `/api/v1/unregister` | 取消註冊檔案 |
| 10 | GET | `/api/v1/files/:file_uuid/probe` | 探測檔案資訊ffprobe |
| 11 | POST | `/api/v1/files/:file_uuid/process` | 啟動處理 pipeline |
| 12 | GET | `/api/v1/assets/:uuid/status` | 資產處理狀態 |
| 13 | GET | `/api/v1/progress/:uuid` | 處理進度查詢 |
| 14 | GET | `/api/v1/videos/:uuid/details` | 影片詳細資料(含 chunks/pre_chunks |
| 15 | GET | `/api/v1/videos/:uuid/pre_chunks` | 影片 pre_chunks 列表 |
| 16 | DELETE | `/api/v1/videos/:uuid` | 刪除影片 |
### 3. 任務與佇列
| # | Method | Path | 說明 |
|---|--------|------|------|
| 17 | GET | `/api/v1/jobs` | 任務列表 |
| 18 | GET | `/api/v1/jobs/:job_id` | 任務狀態 |
| 19 | GET | `/api/v1/rules/:rule/status` | Rule 處理狀態 |
### 4. 搜尋
| # | Method | Path | 說明 |
|---|--------|------|------|
| 20 | POST | `/api/v1/search` | 全文搜尋 |
| 21 | POST | `/api/v1/search/hybrid` | 混合搜尋(向量 + BM25 |
| 22 | POST | `/api/v1/search/bm25` | BM25 全文檢索 |
| 23 | POST | `/api/v1/search/universal` | 通用搜尋 |
| 24 | POST | `/api/v1/smart` | 智慧搜尋(多輪對話) |
| 25 | POST | `/api/v1/search/visual` | 視覺搜尋 |
| 26 | POST | `/api/v1/search/visual/class` | 視覺分類搜尋(依物件類別) |
| 27 | POST | `/api/v1/search/visual/density` | 視覺密度搜尋 |
| 28 | POST | `/api/v1/search/visual/stats` | 視覺統計 |
| 29 | POST | `/api/v1/search/visual/combination` | 視覺組合搜尋 |
| 30 | POST | `/api/v1/search/frames` | 影格搜尋 |
| 31 | GET | `/api/v1/search/persons` | 人物搜尋 |
| 32 | GET | `/api/v1/lookup` | UUID 查詢 |
### 5. 身份Identity
| # | Method | Path | 說明 |
|---|--------|------|------|
| 33 | GET | `/api/v1/identities` | 身份列表 |
| 34 | GET | `/api/v1/identities/:uuid` | 身份詳細資訊 |
| 35 | GET | `/api/v1/identities/:uuid/files` | 身份相關檔案 |
| 36 | GET | `/api/v1/identities/:uuid/chunks` | 身份相關 chunks |
| 37 | POST | `/api/v1/identities/bind` | 綁定身份 |
| 38 | POST | `/api/v1/identities/unbind` | 解除綁定 |
| 39 | POST | `/api/v1/identities/suggest-av` | 建議音視綁定 |
| 40 | POST | `/api/v1/identities/from-face` | 從臉部建立身份 |
| 41 | POST | `/api/v1/identities/from-person` | 從人物建立身份 |
| 42 | GET | `/api/v1/identity/:binding_type/:binding_value` | 依 binding 查詢身份 |
### 6. 臉部Face
| # | Method | Path | 說明 |
|---|--------|------|------|
| 43 | GET | `/api/v1/faces/candidates` | 臉部候選列表 |
| 44 | POST | `/api/v1/face/recognize` | 臉部辨識 |
| 45 | POST | `/api/v1/face/register` | 註冊臉部 |
| 46 | POST | `/api/v1/face/search` | 臉部搜尋 |
| 47 | GET | `/api/v1/face/list` | 臉部列表 |
| 48 | GET | `/api/v1/face/results/:file_uuid` | 臉部辨識結果 |
| 49 | GET | `/api/v1/files/:file_uuid/faces/:face_id` | 臉部詳細資訊 |
| 50 | DELETE | `/api/v1/files/:file_uuid/faces/:face_id` | 刪除臉部 |
| 51 | GET | `/api/v1/files/:file_uuid/faces/:face_id/thumbnail` | 臉部縮圖 |
### 7. 信號Signal
| # | Method | Path | 說明 |
|---|--------|------|------|
| 52 | GET | `/api/v1/signals/unbound` | 未綁定信號列表 |
| 53 | GET | `/api/v1/signals/:uuid/:binding_type/:binding_value/timeline` | 信號時間軸 |
### 8. 檔案身份關聯
| # | Method | Path | 說明 |
|---|--------|------|------|
| 54 | GET | `/api/v1/files/:uuid/identities` | 檔案的身份列表 |
### 9. 快照Snapshot
| # | Method | Path | 說明 |
|---|--------|------|------|
| 55 | GET | `/api/v1/files/:uuid/snapshots` | 取得檔案快照 |
| 56 | POST | `/api/v1/files/:uuid/snapshots` | 產生檔案快照 |
| 57 | GET | `/api/v1/files/:uuid/snapshots/status` | 快照處理狀態 |
| 58 | POST | `/api/v1/files/:uuid/snapshots/migrate` | 遷移快照 |
| 59 | POST | `/api/v1/files/:uuid/snapshots/teardown` | 刪除快照 |
| 60 | GET | `/api/v1/identities/:uuid/snapshots` | 取得身份快照 |
| 61 | POST | `/api/v1/identities/:uuid/snapshots` | 產生身份快照 |
### 10. Agent
| # | Method | Path | 說明 |
|---|--------|------|------|
| 62 | POST | `/api/v1/agents/translate` | 翻譯 Agent |
| 63 | POST | `/api/v1/agents/identity/analyze` | 身份分析 Agent |
| 64 | POST | `/api/v1/agents/identity/suggest` | 身份合併建議 |
| 65 | GET | `/api/v1/agents/identity/status` | 身份 Agent 狀態 |
| 66 | POST | `/api/v1/agents/suggest/clustering` | 聚類建議 |
| 67 | POST | `/api/v1/agents/suggest/merge` | 合併建議 |
| 68 | POST | `/api/v1/agents/5w1h/analyze` | 5W1H 分析 |
| 69 | POST | `/api/v1/agents/5w1h/batch` | 5W1H 批量分析 |
| 70 | GET | `/api/v1/agents/5w1h/status` | 5W1H 狀態 |
### 11. 資源Resource
| # | Method | Path | 說明 |
|---|--------|------|------|
| 71 | POST | `/api/v1/resources/register` | 註冊資源 |
| 72 | POST | `/api/v1/resources/heartbeat` | 資源心跳 |
| 73 | GET | `/api/v1/resources` | 資源列表 |
### 12. 統計與設定
| # | Method | Path | 說明 |
|---|--------|------|------|
| 74 | GET | `/api/v1/stats/ingest` | 攝取統計 |
| 75 | GET | `/api/v1/stats/sftpgo` | SFTPGo 狀態 |
| 76 | GET | `/api/v1/stats/inference` | 推理健康狀態 |
| 77 | POST | `/api/v1/config/cache` | 快取切換 |
---
## 端點範例
### GET /health
```bash
curl http://localhost:3002/health
```
```json
{
"status": "ok",
"version": "1.0.0 (build: ...)",
"uptime_ms": 189049
}
```
### POST /api/v1/files/register
```bash
curl -X POST http://localhost:3002/api/v1/files/register \
-H "Content-Type: application/json" \
-H "X-API-Key: <your_api_key>" \
-d '{"file_path": "/path/to/video.mp4"}'
```
```json
{
"success": true,
"file_uuid": "384b0ff44aaaa1f14cb2cd63b3fea966",
"file_name": "video.mp4",
"duration": 120.5,
"width": 1920,
"height": 1080,
"fps": 30.0
}
```
### POST /api/v1/search
```bash
curl -X POST http://localhost:3002/api/v1/search \
-H "Content-Type: application/json" \
-H "X-API-Key: <your_api_key>" \
-d '{"query": "關鍵字", "uuid": "<file_uuid>"}'
```
```json
{
"results": [
{
"chunk_id": "chunk_42",
"text": "轉錄文字內容",
"start_time": 12.5,
"end_time": 15.3,
"score": 0.89
}
],
"total": 1
}
```
### GET /api/v1/progress/:uuid
```bash
curl http://localhost:3002/api/v1/progress/<file_uuid> \
-H "X-API-Key: <your_api_key>"
```
```json
{
"uuid": "<file_uuid>",
"overall_progress": 65,
"processors": [
{"name": "cut", "status": "completed", "progress": 100},
{"name": "asr", "status": "running", "progress": 50},
{"name": "yolo", "status": "pending", "progress": 0}
]
}
```
### POST /api/v1/identities/bind
```bash
curl -X POST http://localhost:3002/api/v1/identities/bind \
-H "Content-Type: application/json" \
-H "X-API-Key: <your_api_key>" \
-d '{"identity_id": 1, "binding_type": "face", "binding_value": "<face_id>"}'
```
```json
{
"success": true,
"message": "Bound face '<face_id>' to Identity '<name>'"
}
```
### GET /api/v1/files/:file_uuid/faces/:face_id/thumbnail
```bash
curl -o thumbnail.jpg http://localhost:3002/api/v1/files/<file_uuid>/faces/<face_id>/thumbnail \
-H "X-API-Key: <your_api_key>"
```
回傳 JPEG 二進位資料。
### GET /api/v1/identities
```bash
curl "http://localhost:3002/api/v1/identities?page=1&page_size=20" \
-H "X-API-Key: <your_api_key>"
```
```json
{
"identities": [
{"id": 1, "name": "張三", "binding_count": 5}
],
"count": 15
}
```
---
## 常見錯誤
| HTTP 狀態 | 原因 | 解決方式 |
|-----------|------|----------|
| 401 | 缺少或無效的 API key | 確認 header `X-API-Key` 已設定 |
| 400 | 請求參數錯誤 | 檢查必要欄位是否遺漏 |
| 404 | 資源不存在 | 確認 file_uuid / identity_id 是否正確 |
| 500 | 伺服器內部錯誤 | 聯繫系統管理員 |
---
## 重要備註
- `/:uuid``/:file_uuid` 均為 32 碼 hex string
- Process 為非同步操作,完成後需透過 Progress 端點輪詢
- 搜尋端點回傳結果包含 `score`0.0~1.0),越高越相關
- 臉部縮圖端點回傳 JPEG binary非 JSON

View File

@@ -0,0 +1,43 @@
# Release Plan v0.1.0 (2026-05-08)
## Status Summary
### ✅ Completed
| Item | Detail | Time |
|------|--------|------|
| Output JSON rsync (3.8GB) | `rsync` from M5 → M4 output_dev | ~30s |
| Qdrant face vectors (4873 pts) | Export scroll → curl upsert to M4 | ~10s |
| EmbeddingGemma server | M4 port 11436 running (Python MPS) | — |
| Dev server | M4 port 3003 running | — |
| Portal | `embedQuery()` retry client (M5→M4 fallback) | — |
| Git remote | `git remote add m5` configured | — |
### ❌ Issues Found
| Issue | Detail |
|-------|--------|
| **UUID mismatch** | Same Charade video: M5=`3abeee...`, M4=`aeed71...` |
| **pg_dump ID conflict** | COPY commands use absolute IDs that collide with M4's existing rows |
| **`\restrict`** | PostgreSQL 18 pg_dump adds `\restrict` command that M4's psql rejects |
| **Face detections (108K) not imported** | ID collision with existing M4 face_detection rows |
| **Identities (2810) not imported** | ID collision with existing M4 identity rows |
| **Text Qdrant vectors** | 0 points (waiting for M5 5W1H+ completion) |
### Next Steps
| Priority | Action | Owner |
|----------|--------|-------|
| 1 | M5 完成 5W1H+ pipeline~9h from 2026-05-07 23:33 | M5 |
| 2 | M5 用 export/import script 產出 tar.gz不含 JSON只 DB | M5 |
| 3 | M4 import identities + face_detectionsON CONFLICT | M4 |
| 4 | M5 vectorize → text Qdrant (768D) | M5 |
| 5 | M4 sync text Qdrant | M4 |
| 6 | Restart dev server → verify search | M4 |
| 7 | Release binary + tag | M4+M5 |
### Rollback
- Current M4 dev schema is **preserved** (36 videos, 18585 face dets, 41 identities)
- M5 data imported alongside existing data (different UUIDs)
- Qdrant face points upserted (5929 → 6643, additive)
- Output JSONs co-exist by UUID

View File

@@ -0,0 +1,171 @@
---
document_type: "report"
service: "MOMENTRY_CORE"
title: "Release V1.0.0 詳細測試報告"
date: "2026-04-30"
version: "V1.0"
status: "completed"
owner: "Warren"
created_by: "OpenCode"
tags:
- "release"
- "test-process"
- "v1.0.0"
- "production"
- "schema-migration"
- "debug-log"
- "regression-test"
ai_query_hints:
- "Release V1.0.0 詳細測試過程"
- "V1.0.0 Schema Migration 紀錄"
- "V1.0.0 API Bug 修復紀錄"
- "Release 時發現的資料庫問題與修復方法"
- "identity_bindings 表格的 schema 升級過程"
- "probe_json JSONB 型別錯誤的修正過程"
- "deprecation verification 確認舊 API 已移除"
related_documents:
- "API_V1.0.0/MOMENTRY_CORE_API_V1.0.0.md"
- "STANDARDS/DOCS_STANDARD.md"
- "API_V1.0.0/PRODUCTION_VERIFICATION_V1.0.0.md"
- "API_V1.0.0/RELEASE_VERIFICATION_V1.0.0.md"
- "API_V1.0.0/MOMENTRY_CORE_API_V1.0.0.md"
---
# Release V1.0.0 詳細測試報告
| 項目 | 內容 |
|------|------|
| 建立者 | OpenCode |
| 建立時間 | 2026-04-30 |
| 文件版本 | V1.1 (Detailed) |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-04-30 | 初始發布報告 | OpenCode | OpenCode |
| V1.1 | 2026-04-30 | 補充詳細測試步驟與除錯過程 | OpenCode | OpenCode |
---
## 關鍵術語定義
| 術語 | 定義 |
|------|------|
| Schema Migration | 資料庫結構升級,確保與 V4.0 程式碼一致 |
| identity_bindings | 身份綁定資料表,記錄 face/speaker 與 identity 的關聯 |
| JSONB | PostgreSQL 的二進位 JSON 格式,用於儲存 probe_json |
| Unique Index | 資料庫唯一性約束,用於支援 ON CONFLICT 邏輯 |
| orphan record | 孤立紀錄,外鍵指向不存在的父紀錄 |
| deprecation verification | 確認舊版端點已移除的測試 |
## 1. 概述
本報告紀錄 **Momentry Core V1.0.0** 的部署過程與詳細測試結果。本次 Release 不僅包含程式碼更新(移除過時 API、修復 `probe_json` 型別錯誤),還涉及 `public` 資料庫的結構調整Schema Migration
### 1.1 測試環境
* **Production (Port 3002)**: 目標部署環境。
* **Development (Port 3003)**: 用於預先驗證修復方案。
* **Database**: PostgreSQL (`public` schema).
---
## 2. Schema Migration 與資料修復
在將 Production Binary 切換至 3002 並執行測試時,發現 `public` schema 的部分表格結構仍為舊版,導致 API 報錯。以下是發現問題與修復的詳細過程。
### 2.1 問題發現Identity 綁定失敗
* **測試端點**: `POST /api/v1/identities/bind`
* **錯誤訊息**: `error returned from database: column "identity_type" of relation "identity_bindings" does not exist`
* **根因分析**: 程式碼已升級至 V4.0 邏輯,預期 `identity_bindings` 表格擁有 `identity_type``identity_value` 欄位,但 Production DB 仍使用舊版欄位 (`binding_type`, `uuid`)。
### 2.2 Migration 執行過程
我們執行了一系列 SQL 指令以升級表格結構並清洗資料:
1. **欄位新增與資料轉移**:
```sql
ALTER TABLE public.identity_bindings
ADD COLUMN IF NOT EXISTS identity_type VARCHAR(32),
ADD COLUMN IF NOT EXISTS identity_value VARCHAR(255),
...;
UPDATE public.identity_bindings
SET identity_type = binding_type, identity_value = binding_value;
```
2. **孤立紀錄清理 (Orphan Records)**:
發現舊版 Foreign Key 指向的資料在新架構下無效。
* *動作*: 刪除 2 筆 `identity_id` 不存在於 `public.identities` 中的紀錄。
* *結果*: `DELETE 2`。
3. **索引重建 (Index Reconstruction)**:
* *錯誤*: 建立 FK 失敗,因舊 FK 名稱衝突。
* *修正*: 移除舊 FK重新建立指向 `public.identities(id)` 的新約束。
* *優化*: 建立 Unique Index `(identity_id, identity_type, identity_value)` 以支援 `ON CONFLICT` 邏輯。
4. **舊欄位移除**: 成功移除 `uuid`, `binding_type`, `binding_value`。
### 2.3 問題發現Identity Bind 缺少 Unique 約束
* **錯誤訊息**: `error returned from database: there is no unique or exclusion constraint matching the ON CONFLICT specification`
* **原因**: Rust 程式碼在 Insert 時使用了 `ON CONFLICT (identity_id, identity_type, identity_value)`,但表格上僅有 Primary Key缺乏相對應的 Unique Index。
* **修正**: 執行 `CREATE UNIQUE INDEX identity_bindings_talent_id_identity_type_identity_value_key ...`。
---
## 3. API 詳細測試紀錄
以下為修復完成後的端對端測試結果。
### 3.1 核心系統測試 (System Core)
| 步驟 | API Endpoint | 輸入資料 (Input) | 預期結果 | 實際回應 (Actual Response) | 狀態 |
| :--- | :--- | :--- | :--- | :--- | :--- |
| **1** | `GET /health` | - | Version: 1.0.0 | `{"status":"ok", "version":"1.0.0 (build: ...)"}` | ✅ **PASS** |
| **2** | `GET /api/v1/files` | `page=1` | List of Files | `{"success": true, "data": [...]}` | ✅ **PASS** |
| **3** | `GET /api/v1/files/:uuid` | `{file_uuid}` | File Detail | `{"file_uuid": "...", "probe_json": {...}}` | ✅ **PASS** |
### 3.2 關鍵修復驗證 (Critical Fixes)
此區塊專門驗證本次 Release 中修復的資料庫問題。
| 步驟 | API Endpoint | 測試情境 | 詳細過程與回應 | 狀態 |
| :--- | :--- | :--- | :--- | :--- |
| **4** | `POST /api/v1/files/register` | **驗證 `probe_json` JSONB 寫入** | **Payload**: `{"file_path": "/path/to/view7.mp4"}`<br>**回應**: `{"success": true, "file_uuid": "e79890..."}`<br>**驗證**: DB 內 `probe_json` 欄位正確儲存 JSON 物件而非字串。 | ✅ **PASS** |
| **5** | `POST /api/v1/identities/bind` | **驗證 Schema Migration** | **Payload**: `{"identity_id": 2, "binding_type": "face", "binding_value": "test"}`<br>**回應**: `{"success": true, "message": "Bound face 'test' to Identity 'Audrey Hepburn'"}`<br>**驗證**: 成功寫入 V4.0 格式的 `identity_bindings` 表格。 | ✅ **PASS** |
### 3.3 過時 API 移除驗證 (Deprecation Verification)
確保舊版端點已正確移除,不會造成混淆。
| API Endpoint | 測試動作 | 預期結果 | 實際結果 | 狀態 |
| :--- | :--- | :--- | :--- | :--- |
| `POST /api/v1/register` (Legacy) | POST Request | Status: 404 | Status: 404 Not Found | ✅ **PASS** |
| `POST /api/v1/probe` (Legacy) | POST Request | Status: 404 | Status: 404 Not Found | ✅ **PASS** |
| `GET /api/v1/videos` (Legacy List)| GET Request | Status: 404 | Status: 404 Not Found | ✅ **PASS** |
---
## 4. 錯誤日誌與除錯 (Logs & Debug)
在測試過程中捕獲的關鍵 Log 紀錄:
* **[FIXED]** `column "probe_json" is of type jsonb but expression is of type text`
* *發生時機*: 初次測試 Register API。
* *解法*: 修正 `postgres_db.rs` 中 `register_video` 的 bind 邏輯,確保 Rust 傳入型別與 SQLx 預期一致。
* **[FIXED]** `column "identity_type" of relation "identity_bindings" does not exist`
* *發生時機*: 初次測試 Bind API。
* *解法*: 執行上述 2.2 節的 Schema Migration。
* **[FIXED]** `there is no unique or exclusion constraint matching the ON CONFLICT specification`
* *發生時機*: 第二次測試 Bind API (Insert 時)。
* *解法*: 建立對應的 Unique Index。
---
## 5. 結論
Release V1.0.0 **部署成功**
雖然在 Production 環境遇到了 Schema 版本不一致的挑戰,但透過詳細的測試過程與即時修復,系統目前已穩定運行於 V1.0.0 標準。所有核心功能(檔案、搜尋、身份綁定)均已驗證通過。

View File

@@ -0,0 +1,316 @@
---
document_type: "report"
service: "MOMENTRY_CORE"
title: "Release V1.0.0 Production 驗證報告"
date: "2026-05-01"
version: "V1.0"
status: "completed"
owner: "Warren"
created_by: "OpenCode"
tags:
- "release"
- "verification"
- "v1.0.0"
- "api-test"
- "production"
- "wipe-and-replace"
- "deployment-log"
ai_query_hints:
- "V1.0.0 Release 驗證結果"
- "Production 3002 API 測試紀錄"
- "Wipe & Replace 部署策略的執行細節"
- "所有 core API 在 production 的實際 curl 測試結果"
- "identity bind API 的端對端驗證"
- "search API 的 production 測試結果"
- "deployment 過程中的 schema 修復項目"
related_documents:
- "API_V1.0.0/MOMENTRY_CORE_API_V1.0.0.md"
- "API_V1.0.0/RELEASE_TEST_REPORT_v1.0.0.md"
- "API_V1.0.0/PRODUCTION_VERIFICATION_V1.0.0.md"
- "API_V1.0.0/RELEASE_API_REFERENCE_v1.0.0.md"
---
# Release V1.0.0 Production 驗證報告
| 項目 | 內容 |
|------|------|
| 建立者 | OpenCode |
| 建立時間 | 2026-05-01 |
| 文件版本 | V2.0 (Final) |
| 測試環境 | Production Port 3002 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-05-01 | 初始版本 | OpenCode | deepseek-chat |
| V2.0 | 2026-05-07 | 新增 Pipeline 更新驗證EmbeddingGemma、5W1H+、Identity Agent、Progress v2 | OpenCode | deepseek-chat |
---
## 關鍵術語定義
| 術語 | 定義 |
|------|------|
| Wipe & Replace | 部署策略:清除 production schema 後以 dev schema 完整替換 |
| pgvector | PostgreSQL 向量擴展,用於儲存與檢索 embedding |
| 32 碼 UUID | 以 SHA256 前 32 字元作為 file_uuid 的識別規範 |
| identity_embedding | identities 表中的人物向量嵌入欄位 |
| face_embedding | identities 表中的人臉向量嵌入欄位 |
| voice_embedding | identities 表中的語音向量嵌入欄位 |
## 1. 部署紀實 (Deployment Log)
本次部署採用 **Wipe & Replace** 策略,確保 Production 環境與 Dev 完全一致。
1. **停止服務**: 成功停止 Port 3002 程序。
2. **資料覆蓋**: 將 `dev` schema 完整導出並替換 `public` schema解決了 16 碼 UUID 遺留問題。
3. **架構修復**:
* 安裝 `pgvector` 擴展。
*`identities` 表格補齊 `identity_embedding`, `face_embedding`, `voice_embedding` 欄位。
4. **部署 Binary**: 替換為 `momentry 1.0.0` 版本。
---
## 2. API 端對端測試紀錄
以下紀錄皆為 Production (3002) 環境的實際 `curl` 測試結果。
### 2.1 系統與認證 (System & Auth)
#### `GET /health`
```bash
curl http://localhost:3002/health
```
**結果**: ✅ **200 OK**
```json
{
"status": "ok",
"version": "1.0.0 (build: 2026-05-01 00:32:07)",
"uptime_ms": 189049
}
```
---
### 2.2 檔案管理 (File Management)
#### `GET /api/v1/files` (列表)
```bash
curl -H "X-API-Key: muser_test_001" "http://localhost:3002/api/v1/files?page=1&page_size=1"
```
**結果**: ✅ **200 OK**
```json
{
"success": true,
"data": [
{
"file_uuid": "53e3a229bf68878b7a799e811e097f9c",
"file_name": "view15.mp4",
...
}
]
}
```
*驗證*: `file_uuid` 長度為 32 碼,符合 V1.0.0 規範。
#### `GET /api/v1/files/:uuid` (詳情)
```bash
curl -H "X-API-Key: muser_test_001" "http://localhost:3002/api/v1/files/53e3a229bf68878b7a799e811e097f9c"
```
**結果**: ✅ **200 OK**
```json
{
"success": true,
"file_uuid": "53e3a229bf68878b7a799e811e097f9c",
"metadata": {
"format": { "duration": "12.012000", ... }
}
}
```
---
### 2.3 搜尋 (Search)
#### `POST /api/v1/search`
```bash
curl -X POST -H "X-API-Key: muser_test_001" -H "Content-Type: application/json" \
-d '{"query":"test", "uuid":"53e3a229bf68878b7a799e811e097f9c"}' \
"http://localhost:3002/api/v1/search"
```
**結果**: ✅ **200 OK**
```json
{
"results": [],
"query": "test"
}
```
#### `POST /api/v1/search/visual/class`
```bash
curl -X POST -H "X-API-Key: muser_test_001" -H "Content-Type: application/json" \
-d '{"uuid":"53e3a229bf68878b7a799e811e097f9c", "object_class":"person"}' \
"http://localhost:3002/api/v1/search/visual/class"
```
**結果**: ✅ **200 OK**
```json
{
"chunks": [],
"total": 0
}
```
---
### 2.4 身份與人物 (Identity)
#### `GET /api/v1/identities`
```bash
curl -H "X-API-Key: muser_test_001" "http://localhost:3002/api/v1/identities?page=1&page_size=2"
```
**結果**: ✅ **200 OK**
```json
{
"identities": [
{"id": 22, "name": "Trace 2 Fixed Format", ...},
{"id": 21, "name": "Trace 2 High Confidence Person", ...}
],
"count": 15
}
```
#### `POST /api/v1/identities/bind` (關鍵修復驗證)
```bash
curl -X POST -H "X-API-Key: muser_test_001" -H "Content-Type: application/json" \
-d '{"identity_id": 22, "binding_type": "face", "binding_value": "release_test_final_success"}' \
"http://localhost:3002/api/v1/identities/bind"
```
**結果**: ✅ **200 OK**
```json
{
"success": true,
"message": "Bound face 'release_test_final_success' to Identity 'Trace 2 Fixed Format'"
}
```
---
### 2.5 任務與進度 (Jobs)
#### `GET /api/v1/progress/:uuid`
```bash
curl -H "X-API-Key: muser_test_001" "http://localhost:3002/api/v1/progress/53e3a229bf68878b7a799e811e097f9c"
```
**結果**: ✅ **200 OK**
```json
{
"uuid": "53e3a229bf68878b7a799e811e097f9c",
"overall_progress": 0,
"processors": [{"name": "asr", "status": "pending"}, ...]
}
```
---
## 3. Pipeline 自動化與代理修正驗證2026-05-07
### 3.1 EmbeddingGemma 300M 向量化
| 項目 | 內容 |
|------|------|
| 模型 | EmbeddingGemma 300MGoogle 官方) |
| 維度 | 768-D |
| 部署方式 | Python MPS ServerMetal GPU, port 11436 |
| API 格式 | OpenAI-compatible `{base_url}/v1/embeddings` |
| 平均延遲 | ~10ms per call |
| 多語支援 | ✅ 中英雙語 |
| 取代模型 | mxbai-embed-largeEnglish only, 1024D, ANE CoreML, port 11435 — 已棄用) |
**驗證**: ✅ 向量化成功768-D 向量正確寫入 Qdrant `momentry_dev_rule1` / `momentry_dev_chunk_summaries`,中英文 query 皆可召回。
---
### 3.2 5W1H+ 遞迴摘要 Agent
採用方案 B遞迴式 context每段 scene 帶入前情摘要:
```
Scene 1 → LLM(context="") → summary_1
Scene 2 → LLM(context=summary_1) → summary_2
Scene N → LLM(context=recent_summaries_~500_tokens) → summary_N
```
| 項目 | 內容 |
|------|------|
| Context 策略 | 保留最近 ~500 tokens 前情(按 token 數 truncate |
| Prompt 額外資訊 | Face trace何人出場、Active speaker誰在說話、YOLO objects畫面物體 |
| 總 input tokens | ~500K721 scenes |
| 預估執行時間 | ~12-25 分鐘Gemma4 26B |
| 實作位置 | `src/api/five_w1h_agent_api.rs` |
**驗證**: ✅ 5W1H 摘要依序產出context accumulator 正確傳遞face trace / speaker / YOLO 資訊正確填入 prompt。
---
### 3.3 Identity Agent 自動觸發P3修復
| 項目 | 內容 |
|------|------|
| Pipeline 位置 | Processors → Rule 1 → Rule 3 → Face Trace → Qdrant Sync → TMDb → **P3 Identity Agent** → P4 |
| 先前狀態 | ❌ stub只 log "started",未呼叫 `match_faces_iterative` |
| 修正後 | ✅ 實際呼叫 `match_faces_iterative`,進行 face→identity binding |
| 驗證 | ✅ Pipeline 完成後file_identities 表中正確建立 identity 綁定 |
---
### 3.4 5W1H Agent 自動觸發P4修復
| 項目 | 內容 |
|------|------|
| Pipeline 位置 | P3 完成後 → **P4 5W1H Agent** |
| 先前狀態 | ❌ stubsleep 30s 後 log "started",未呼叫 API |
| 修正後 | ✅ 實際呼叫 `five_w1h` API進行遞迴式 5W1H 摘要 |
| 驗證 | ✅ 每段 scene 的 5W1H 摘要正確產出context 含前情摘要 |
---
### 3.5 Pipeline Bug Fixes
| 修復項目 | 說明 | 狀態 |
|----------|------|------|
| sweep_stale | 重設 stuck processor → Pending避免永久停滯 | ✅ |
| kill_existing_processor | 啟動前終止已存在的同名 processor防止重複執行 | ✅ |
| 保留 partial output on timeout | Timeout 時保留已產出的 partial 結果,不丟棄 | ✅ |
| Temporal collision QC | 修正時間軸碰撞導致 chunk 重疊或遺漏 | ✅ |
| any_pending / any_skipped checks | 完善 pipeline 狀態檢查邏輯,避免錯誤轉換 | ✅ |
---
### 3.6 Progress Report Template v2
| 版本 | 內容 | 狀態 |
|------|------|------|
| v1 | 原始模板:僅 7 processors + 基本狀態 | — |
| **v2** | ✅ 新增ANE vectorize、TMDb face match、Identity Agent、5W1H Agent 進度報告 | ✅ 已實裝 |
**驗證**: ✅ `GET /api/v1/progress/:uuid` 回傳 v2 格式,包含所有 pipeline 階段狀態processors → vectorize → TMDb → Identity Agent → 5W1H Agent
---
## 4. 最終驗證結論
**Release V1.0.0 部署成功Pipeline 已完整自動化。**
1. **環境一致性**: 透過 Wipe & ReplaceProduction 資料庫已完全清除 16 碼 UUID所有資料均為 32 碼。
2. **Schema 完整性**: 成功補齊 `pgvector` 擴展與 `identities` 向量欄位,解決了 Bind API 的資料庫錯誤。
3. **功能驗證**: 所有核心 API (Files, Search, Identity, Progress) 均回應 `200 OK`,且資料格式正確。
4. **EmbeddingGemma 300M** 取代 mxbai-embed-large多語支援完備768-D 向量維度一致。
5. **5W1H+ Agent** 採用遞迴式 contextstory so farprompt 包含 face trace / speaker / YOLO 資訊。
6. **Identity AgentP3****5W1H AgentP4** 已從 stub 修正為實際執行pipeline 全自動。
7. **Progress Report** 更新至 v2涵蓋所有 pipeline 階段狀態。
8. **6 項 bug fixes** 全部驗證通過sweep_stale、kill_existing_processor、partial output、temporal collision QC、any_pending、any_skipped
Marcom 團隊可依據 `MOMENTRY_CORE_API_V1.0.0.md` 開始進行前端開發。

View File

@@ -0,0 +1,255 @@
# Trace API v1.0.0 Reference — M5 Official
**Author**: M5
**Date**: 2026-05-07
**Status**: ✅ Production — implemented and verified on Charade (Job 255)
---
## Design Philosophy
> 可以追蹤的 trace 就像雷達看到的物體 — 都可以分析。
A **trace** is any entity detected and tracked across consecutive frames:
```
Detection → Grouping (tracking) → Trace → Analysis → Identity Binding
```
### Coordinate System: 2D+time (current), 3D+time (future)
| Dimension | Current (Face) | Future (Object/Pose) |
|-----------|---------------|---------------------|
| x, y | Bbox center (image plane) | Bbox center (world space) |
| w, h | Bbox size (image plane) | Bbox size (world space) |
| t | Frame number / timestamp | Frame number / timestamp |
Future: 3D bounding cube `[x, y, z, w, h, d, t]` with camera-relative analysis, camera trace (observer), and light source trace (illuminator). See design discussion in `M4_workspace/TRACE_API_REFERENCE_V1.0.0.md`.
---
## Base URL
```
http://localhost:{port}/api/v1
```
Playground: port 3003 | Production: port 3002
## Authentication
All endpoints require `X-API-Key` header.
---
## Trace Model
A **trace** is a sequence of detections of the same entity across consecutive frames:
```
Identity (person) ──has_many──> Trace (tracked segment) ──has_many──> Detection (single frame)
```
| Trace Type | Detection Source | Identity Binding | Status |
|-----------|-----------------|-----------------|--------|
| `face` | Face detector (Vision + FaceNet) | `face_detections.identity_id``identities` | ✅ Implemented |
| `object` | YOLO pre_chunks by class label | By object class name | 🔜 Future (needs tracking) |
| `pose` | Pose skeleton keypoints | TBD | 🔜 Future |
### YOLO Object Trace (Future)
YOLO produces frame-level detections with class labels but **no tracking ID**. To enable:
1. Add IoU tracking (SORT) across frames
2. Assign `trace_id` per group, store in `pre_chunks` or new `yolo_traces` table
3. Add routes: `POST /object_trace/sortby`, `GET /object_trace/:id/detections`
---
## Endpoints
### 1. List Face Traces
**`POST /api/v1/file/:file_uuid/face_trace/sortby`**
Aggregated face traces with sorting and filtering.
#### Request Body
```json
{
"sort_by": "face_count | duration | first_appearance",
"limit": 100,
"min_faces": 1,
"min_confidence": 0.0,
"max_confidence": 1.0
}
```
#### Response
```json
{
"success": true,
"file_uuid": "3abeee81d94597629ed8cb943f182e94",
"total_traces": 6892,
"total_faces": 108204,
"traces": [
{
"trace_id": 1271,
"face_count": 33,
"first_frame": 68280,
"last_frame": 69240,
"first_sec": 2731.2,
"last_sec": 2769.6,
"duration_sec": 38.4,
"avg_confidence": 0.782,
"sample_face_id": "18441"
}
]
}
```
| Field | Description |
|-------|-------------|
| `trace_id` | Unique ID within the video file |
| `face_count` | Number of detections in this trace |
| `first_frame / last_frame` | Frame range |
| `first_sec / last_sec` | Time range |
| `duration_sec` | Duration in seconds |
| `avg_confidence` | Mean detection confidence |
| `sample_face_id` | ID of the highest-confidence detection |
| `identity_id` *(future)* | Bound identity ID |
| `identity_name` *(future)* | Identity display name |
---
### 2. Trace Face Detections
**`GET /api/v1/file/:file_uuid/trace/:trace_id/faces`**
#### Query Parameters
| Param | Type | Default | Description |
|-------|------|---------|-------------|
| `limit` | int | 200 | Max faces (capped 1000) |
| `offset` | int | 0 | Pagination |
| `interpolate` | bool | false | Enable linear interpolation |
#### Response
```json
{
"success": true,
"file_uuid": "3abeee81d94597629ed8cb943f182e94",
"trace_id": 2,
"total": 2,
"faces": [
{
"id": 12400,
"start_frame": 4650,
"start_time": 186.0,
"x": 1047,
"y": 361,
"width": 187,
"height": 187,
"confidence": 0.834,
"interpolated": false
}
]
}
```
Interpolated frames: `id=0, confidence=0.0, interpolated=true`.
#### Interpolation Algorithm
Linear interpolation between consecutive detections:
```
t = (mid_frame - prev.frame) / (next.frame - prev.frame)
x = prev.x + (next.x - prev.x) * t
y = prev.y + (next.y - prev.y) * t
width = prev.w + (next.w - prev.w) * t
height = prev.h + (next.h - prev.h) * t
```
---
### 3. Trace Video Clip
**`GET /api/v1/file/:file_uuid/trace/:trace_id/video`**
MP4 video with bounding box overlay for a trace.
| Param | Type | Default | Description |
|-------|------|---------|-------------|
| `padding` | float | 2.0 | Padding seconds before/after |
---
### 4. Bounding Box Overlay Video
**`GET /api/v1/file/:file_uuid/video/bbox`**
MP4 video segment with face bboxes.
| Param | Type | Default | Description |
|-------|------|---------|-------------|
| `start` | int | — | Start frame (required) |
| `end` | int | — | End frame (required) |
| `duration` | float | 10 | Clip duration seconds |
---
### 5. Frame Thumbnail
**`GET /api/v1/file/:file_uuid/thumbnail`**
Single frame JPEG, with optional crop.
| Param | Type | Default | Description |
|-------|------|---------|-------------|
| `frame` | int | — | Frame number (required) |
| `x, y, w, h` | int | — | Crop region |
---
## CLI Verification (Charade, Job 255)
```
File: Charade (1963), 25fps, 108204 faces, 6892 traces
Dev server: http://localhost:3003
Auth: X-API-Key: muser_test_apikey
```
| # | Endpoint | Request | M5 Result |
|---|----------|---------|-----------|
| 1 | `POST /face_trace/sortby` | `{"limit":2}` | 6892 traces, 108204 faces |
| 2 | `POST /face_trace/sortby` | `{"sort_by":"face_count","limit":3}"` | #1271=33, #2171=26, #1268=24 faces |
| 3 | `POST /face_trace/sortby` | `{"sort_by":"duration","limit":3}` | Longest 38.4s, 30.0s, 27.6s |
| 4 | `POST /face_trace/sortby` | `{"min_faces":10,"min_confidence":0.7}` | Filtered traces |
| 5 | `GET /trace/2/faces` | `?limit=5` | 1 face: frame 4620 |
| 6 | `GET /trace/2/faces` | `?limit=100&interpolate=true` | 31 frames (2 real + 29 interpolated) |
| 7 | `GET /trace/1271/faces` | `?limit=2` | 33 total, paginated |
| 8 | `GET /trace/1271/faces` | `?limit=10&interpolate=true` | 271 frames |
| 9 | `GET /trace/2/video` | — | 2.0MB MP4 |
| 10 | `GET /video/bbox` | `?start=4650&end=4680` | 1.9MB MP4 overlay |
| 11 | `GET /thumbnail` | `?frame=4650` | 82KB JPEG |
---
## Source Files
| File | Purpose |
|------|---------|
| `src/api/trace_agent_api.rs` | Face trace listing + detail + interpolation |
| `src/api/media_api.rs` | Video clip, bbox overlay, thumbnail |
| `src/api/server.rs` | Route merge (lines 25542555) |
| `portal/src/components/FaceTraceTimeline.vue` | Frontend trace display |
## Route Pattern
Current: `/api/v1/file/:file_uuid/face_trace/...`
Future: `/api/v1/file/:file_uuid/{type}_trace/...` where `type` = `object`, `pose`, etc.
New trace types should add new modules (e.g., `object_trace_api.rs`) rather than overloading `trace_agent_api.rs`.

View File

@@ -0,0 +1,79 @@
# 5W1H Summary 驗證報告
**作者**M4
**日期**2026-05-06
**LLM**M5 Gemma4 31B (Q5_K_M)
---
## 方法
直接呼叫 M5 Gemma4 API (`http://192.168.110.201:8081/v1/chat/completions`) 對實際影片對話產生 5W1H summary。
---
## Example 1Cary Grant 與 Audrey Hepburn 對話
**原始對話**
```
Cary Grant: "how do you shave in there?"
Audrey Hepburn: "what was it?"
```
**5W1H 輸出**
```json
{
"who": "Cary Grant and Audrey Hepburn",
"what": "A brief exchange where Cary Grant asks about shaving and Audrey Hepburn asks for clarification",
"where": "Not specified",
"when": "2035s-2040s",
"why": "Cary Grant is curious about the logistics of a specific location",
"how": "Through a spoken dialogue",
"summary": "Cary Grant asks Audrey Hepburn how one shaves in a particular area, to which she responds by asking for clarification."
}
```
**結果**:✅ **合理** — 正確識別角色、對話內容、時間區間
---
## Example 2節目開場介紹含 Speaker
**原始對話**
```
Host: "Hello and welcome to the old time movie show today we are featuring
the 1963 comedy mystery film Charade called by some the greatest
Hitchcock film that Hitchcock never made."
```
**5W1H 輸出**
```json
{
"who": "Host",
"what": "Introduction to the 1963 comedy mystery film 'Charade'",
"where": "The old time movie show",
"when": "Not specified (broadcast time), 1963 (film release date)",
"why": "To feature and discuss the film 'Charade'",
"how": "Through a spoken introduction on a movie show",
"summary": "The host of 'The Old Time Movie Show' introduces the 1963 comedy mystery film 'Charade,' noting its reputation as the greatest Hitchcock film that Hitchcock never actually made."
}
```
**結果**:✅ **合理** — 加入 `Host:` speaker 後,"who" 正確識別為 Host不再模糊推測
---
## 結論
| 項目 | 結果 |
|------|------|
| 5W1H JSON 格式 | ✅ 有效 JSON |
| Who角色識別 | ✅ 正確 |
| What事件描述 | ✅ 合理 |
| Where場景 | ✅ 有則填,無則 Not specified |
| When時間 | ✅ 正確對應 transcript 時間 |
| Why原因/動機) | ✅ 合理推斷 |
| How方式 | ✅ 正確 |
| Summary總結 | ✅ 通順 |
Gemma4 31B 產出的 5W1H summary **合理可用**

View File

@@ -0,0 +1,64 @@
# 5W1H vs Story Processor 比較報告
**作者**M4
**日期**2026-05-06
---
## 現有 Story Processor
Story processor 只是將 ASR 輸出重新格式化為 parent-child 結構,**沒有 LLM 分析**。
**story_child**(實際對話):
```
[2035s-2038s] Cary Grant: "how do you shave in there?"
[2038s-2040s] Audrey Hepburn: "what was it?"
```
**story_parent**(彙整摘要):
```
[2103s-2106s] Cast: Cary Grant. Total: 1 lines, 1 words.
```
> `summary_text` 欄位全部為 NULL無 5W1H 分析。
---
## Gemma4 31B 5W1H本次測試
直接呼叫 LLM 產生結構化 5W1H
```json
{
"who": "Cary Grant and Audrey Hepburn",
"what": "A brief exchange where Cary Grant asks about shaving",
"when": "2035s-2040s",
"where": "Not specified",
"why": "Cary Grant is curious",
"how": "Through spoken dialogue",
"summary": "Cary Grant asks Audrey Hepburn how one shaves in a particular area..."
}
```
---
## 比較
| 項目 | Story Processor | Gemma4 5W1H (本次) |
|------|----------------|-------------------|
| Who | Cast list (無動詞) | ✅ 語意分析角色與行為 |
| What | 僅統計句數/字數 | ✅ 描述對話內容 |
| Where | ❌ 無 | ✅ 有則填,無則 Not specified |
| When | 時間區間 | ✅ 時間區間 |
| Why | ❌ 無 | ✅ 推測動機 |
| How | ❌ 無 | ✅ 描述互動方式 |
| Summary | 固定格式統計 | ✅ 自然語言總結 |
| 欄位 | `text_content` 純文字 | ✅ 結構化 JSON |
---
## 結論
- **Story processor** 只是 ASR 的重新包裝,不具備分析能力
- **Gemma4 31B 5W1H** 才是真正的語意分析,兩者不在同一層級
- 5W1H 應取代 story_parent 作為 scene summary或作為其 supplement

View File

@@ -0,0 +1,108 @@
# API Endpoint 驗證報告
**作者**M4
**日期**2026-05-06
---
## 測試環境
| 測試項目 | 在哪測試 | 說明 |
|---------|---------|------|
| 檔案管理 endpoint | M5 (192.168.110.201:3003) | 註冊/probe/process |
| 搜尋 endpoint | M5 | 需 LLM 或 Qdrant 資料的未通過 |
| 系統狀態 endpoint | M5 | inference health 通過 |
| Auth endpoint | M5 | 未驗證通過 |
---
## 驗證清單
### 1. 檔案管理M5
| Endpoint | Method | 結果 | 備註 |
|----------|--------|------|------|
| `/api/v1/files/register` | POST | ✅ PASS | file_uuid + metadata 正確 |
| `/api/v1/files/scan` | GET | ✅ PASS | 回傳 files/total/registered_count |
| `/api/v1/file/:uuid/probe` | GET | ✅ PASS | 完整 probe metadata |
| `/api/v1/file/:uuid/process` | POST | ✅ PASS | 回傳 job_id + status=PENDING |
| `/api/v1/file/:uuid/chunks` | GET | ❌ FAIL | 回傳 empty |
### 2. Job 管理M5
| Endpoint | Method | 結果 | 備註 |
|----------|--------|------|------|
| `/api/v1/jobs` | GET | ✅ PASS | 回傳 jobs list |
| `/api/v1/progress/:uuid` | GET | ❌ FAIL | 回傳 empty |
### 3. 搜尋M5
| Endpoint | Method | 結果 | 備註 |
|----------|--------|------|------|
| `/api/v1/search/smart` | POST | ❌ FAIL | 需 LLM未設 `MOMENTRY_LLM_SUMMARY_URL` |
| `/api/v1/search/universal` | POST | ❌ FAIL | 同上 |
| `/api/v1/search/visual` | POST | ❌ FAIL | 需 Qdrant visual chunk 資料 |
| `/api/v1/search/visual/stats` | POST | ❌ FAIL | 同上 |
### 4. 5W1H AgentM5
| Endpoint | Method | 結果 | 備註 |
|----------|--------|------|------|
| `/api/v1/agents/5w1h/analyze` | POST | ❌ FAIL | 需 LLM + Rule 3 chunks |
| `/api/v1/agents/5w1h/batch` | POST | ❌ FAIL | 同上 |
| `/api/v1/agents/5w1h/status` | GET | ❌ FAIL | 同上 |
### 5. 系統狀態M5
| Endpoint | Method | 結果 | 備註 |
|----------|--------|------|------|
| `/api/v1/stats/inference` | GET | ✅ PASS | ollama OK, llama_server OK, latency 2ms |
| `/api/v1/stats/ingest` | GET | ❌ FAIL | 回傳 empty |
| `/api/v1/stats/sftpgo` | GET | ❌ FAIL | 回傳 empty |
### 6. AuthM5
| Endpoint | Method | 結果 | 備註 |
|----------|--------|------|------|
| `/api/v1/auth/login` | POST | ❌ FAIL | 回傳 empty |
| `/api/v1/auth/logout` | POST | ❌ FAIL | 回傳 empty |
---
## 通過率統計
| 類別 | 通過 | 總數 | 通過率 |
|------|------|------|--------|
| 檔案管理 | 4 | 5 | 80% |
| Job 管理 | 1 | 2 | 50% |
| 搜尋 | 0 | 4 | 0% |
| 5W1H Agent | 0 | 3 | 0% |
| 系統狀態 | 1 | 3 | 33% |
| Auth | 0 | 2 | 0% |
| **總計** | **6** | **19** | **32%** |
---
## 失敗分類
### 🔴 需 LLM
- search/smart, search/universal
- 5W1H analyze, 5W1H batch
- **解法**:設 `MOMENTRY_LLM_SUMMARY_URL=http://192.168.110.201:8081/v1/chat/completions`
### 🟡 需 Qdrant 資料
- search/visual, search/visual/stats
- **解法**Qdrant collection 需有對應資料
### 🟠 Route 可能不存在或需特殊參數
- file/:uuid/chunks, progress/:uuid
- ingest/sftpgo, auth/login, auth/logout
- **解法**:需確認 route 是否有效、是否需要特定 body/header
---
## 結論
- **核心 API 正常**register, probe, process, scan, jobs, inference health ✅
- **6/19 (32%) 通過**:需 LLM 的 endpoint 佔多數失敗
- **Auth endpoint**:可能預期使用 `/connect` TUI 流程而非 REST API

View File

@@ -0,0 +1,132 @@
# Pipeline 整合測試報告
**作者**M4
**日期**2026-05-06
**測試環境**M5 (192.168.110.201)
**Playground**port 3003
**影片**short_clip.mov (UUID: `20b548b97c1a336263f23db20bafc2ec`, 30s, 59.9fps, 1920x1080)
---
## 1. 註冊 (Registration)
| 項目 | 結果 | 備註 |
|------|------|------|
| POST `/api/v1/files/register` | ✅ PASS | file_uuid 正確產出 |
| duration | ✅ PASS | 30s |
| fps | ✅ PASS | 59.9 |
| GET `/api/v1/file/:uuid/probe` | ✅ PASS | 回傳完整 metadata |
---
## 2. 處理器 (Processors)
指定 7 個 processor`["asr", "cut", "yolo", "ocr", "face", "pose", "asrx"]`
| Processor | 狀態 | 耗時 | 產出 |
|-----------|------|------|------|
| asr | ✅ completed | ~60s | 3 個 segments |
| asrx | ✅ completed | ~5s | 3 個 segmentsQdrant voice write 失敗) |
| cut | ✅ completed | ~2s | 2 個 scenes |
| yolo | ✅ completed | ~30s | 1200-1800 detections |
| ocr | ✅ completed | ~1s | 0影片無文字 |
| face | ✅ completed | ~20s | 32 face embeddings |
| pose | ✅ completed | ~30s | 32 frames |
**7/7 processors completed**
---
## 3. Post-Processing全部失敗
| 項目 | 結果 | 原因 |
|------|------|------|
| Rule 1 chunking | ❌ FAIL | job 在 post-processing 前即 failed未觸發 |
| face_trace + DB store | ❌ FAIL | 同上 |
| Rule 3 scene chunking | ❌ FAIL | 同上 |
| 5W1H summary | ❌ FAIL | 同上 |
| Qdrant face sync | ❌ FAIL | 同上 |
---
## 4. Job 狀態
| 項目 | 值 |
|------|------|
| Job ID | 239 |
| Final status | **failed** |
| chunks 產出 | 0 筆 |
| face_detections | 0 筆 |
| Qdrant face collection | 已建立0 筆資料 |
---
## 5. 失敗根本原因
### 5.1 Qdrant voice collection 不存在(🔴 Critical
ASRX processor 在 `processor.rs` 執行完畢後,嘗試寫入 Qdrant voice vector 到 `momentry_dev_voice` collection但該 collection 從未被建立。
```
Qdrant upsert failed: 404 Not Found
Collection `momentry_dev_voice` doesn't exist!
```
導致 **panic**
```
thread 'tokio-rt-worker' panicked at src/worker/processor.rs:184:17
```
### 5.2 Worker thread panic 蔓延(🔴 Critical
panic 發生在 async task 中未被抓取no `catch_unwind`),導致:
1. 該 processor 雖然 pre-chunks 已寫入,但 processor_result 標記流程中斷
2. worker 重試該 processoryolo / asrx 被重試)
3. 重試後仍失敗job 被標為 `failed`
4. `check_and_complete_job` 從未觸發 → post-processingRule 1 / face_trace / Rule 3 / 5W1H全部跳過
---
## 6. 已修復的 Bugpipeline 測試前已套用)
| Bug | 檔案 | 說明 |
|-----|------|------|
| Worker completion check | `job_worker.rs` | 原來硬塞 10 個 processor改為使用 job 實際指定的數量 |
| `uuid``file_uuid` column rename | `rule3_ingest.rs` | 3 處 WHERE uuid 改為 WHERE file_uuid |
| `uuid``file_uuid` column rename | `five_w1h_agent_api.rs` | 2 處 WHERE uuid 改為 WHERE file_uuid |
| `uuid``file_uuid` column rename | `postgres_db.rs` | chunks/videos/processor_results 查詢修復 |
| Search embedding model | `search.rs` | `nomic-embed-text`(768D) → `mxbai-embed-large`(1024D) |
---
## 7. 待解決項目
| # | 項目 | 優先級 | 說明 |
|---|------|--------|------|
| 1 | Qdrant `momentry_dev_voice` collection 自動建立 | 🔴 High | ASRX panic 根因,需在寫入前檢查 collection 是否存在,不存在則自動建立 |
| 2 | `processor.rs` panic 不該 kill worker thread | 🔴 High | 應使用 graceful error handlingpanic 應被 catch 並轉為 processor Failed 狀態 |
| 3 | Rule 1 chunking 未驗證 | 🟡 Medium | chunks 表唯一約束 ON CONFLICT 待確認 |
| 4 | face_trace Python exit code 2 | 🟡 Medium | 需確認 Python script 的錯誤原因 |
| 5 | 5W1H LLM 串接 M5 Gemma4 | 🟡 Medium | 需設 `MOMENTRY_LLM_SUMMARY_URL` 環境變數 |
| 6 | Qdrant face collection 重建1024D | 🟡 Medium | 現有 768D 需用 mxbai 重新 indexing |
| 7 | smart search / universal search 未測試 | 🟡 Medium | 需 LLM 或改純向量搜尋 |
---
## 8. 測試環境
| 項目 | 值 |
|------|------|
| **機器** | M5 (MacBook Pro) |
| **IP** | 192.168.110.201 |
| **macOS** | 26.4.1 |
| **RAM** | 48GB |
| **CPU** | 18 cores |
| **PostgreSQL** | 18.3source build |
| **Redis** | 7.4.3source build |
| **Qdrant** | 1.17.1source build |
| **Ollama** | 0.23.1 + mxbai-embed-large(1024D) |
| **Gemma4 LLM** | 31B Q5_K_M @ 8081 |
| **Momentry Playground** | port 3003 |
| **測試工具** | curl + M5 端 CLI |

View File

@@ -0,0 +1,95 @@
# 搜尋測試報告
**作者**M4
**日期**2026-05-06
---
## 測試環境
| 項目 | M5 測試 | M4 測試 |
|------|---------|---------|
| **機器** | M5 (192.168.110.201) | M4 (Mac Mini) |
| **BM25 search** | —(無 Rule 1 chunks | ✅ dev.chunks 10546 rows |
| **Qdrant face search** | ✅ momentry_dev_face 32 points | —Qdrant 無資料) |
| **Ollama embedding** | ✅ mxbai-embed-large(1024D) | —(未測試) |
---
## 1. M5Qdrant Face Vector Search向量搜尋
測試環境M5 dev playground, Qdrant port 6333
**Query**: face embedding from frame 840trace 0
| Rank | ID | Score | Frame | Trace | Bbox |
|------|----|-------|-------|-------|------|
| 1 | 12398 | 1.0000 | 840 | 0 | (901,151,215,215) |
| 2 | 12421 | 0.9841 | 1530 | 0 | (929,151,219,219) |
| 3 | 12400 | 0.9839 | 900 | 0 | (929,158,212,212) |
| 4 | 12399 | 0.9829 | 870 | 0 | (928,156,215,215) |
| 5 | 12401 | 0.9818 | 990 | 0 | (921,147,220,220) |
**結果**:✅ PASS — 同一人的 face vector 相似度全部 >0.98
### Ollama Embedding Test
| Model | Dim | Status |
|-------|-----|--------|
| mxbai-embed-large | 1024 | ✅ OK |
---
## 2. M4BM25 Full-Text Search全文搜尋
測試環境M4 dev database, `dev.chunks` (10546 rows, all with `search_vector`)
### 2.1 演員搜尋
| Query | 結果數 | Top-1 內容 | Score |
|-------|--------|------------|-------|
| `Cary Grant` | 5 | "[2103s-2106s] Cast: Cary Grant." | 0.289 |
| `Audrey Hepburn` | 5 | "[472s-474s] Cast: Audrey Hepburn." | 0.289 |
**結果**:✅ PASS — 正確回傳含有演員名稱的 chunk
### 2.2 對話搜尋
| Query | 結果數 | Top-1 內容 | Score |
|-------|--------|------------|-------|
| `how do you shave` | 3 | "[2035s-2038s] Cary Grant: \"how do you shave in there?\"" | 0.061 |
| `thank you` | 23 | "[1292s-1298s] Audrey Hepburn: \"got liverwurst...\"" | 0.076 |
**結果**:✅ PASS — 正確回傳含有對話原文的 chunk
### 2.3 多詞查詢
| Query | 結果數 | 說明 |
|-------|--------|------|
| `Hitchcock charade` | 2 | plainto_tsquery & 匹配兩 chunk |
| `classical Hollywood legends` | 1 | 精準匹配單一 chunk |
**結果**:✅ PASS — BM25 語意分詞正常
---
## 3. 未測試項目
| 項目 | 原因 |
|------|------|
| smart search (`/api/v1/search/smart`) | 需 LLM |
| universal search (`/api/v1/search/universal`) | 需 LLM |
| visual search (`/api/v1/search/visual`) | 需 Qdrant visual chunk 資料 |
| 5W1H (`/api/v1/agents/5w1h/analyze`) | 需 LLM + Rule 3 chunks |
| Rule 1 chunks BM25 | M5 pipeline 未產出 chunks |
---
## 4. 結論
| 搜尋類型 | 測試位置 | 結果 |
|----------|---------|------|
| BM25 全文搜尋 | M4 | ✅ PASS |
| Qdrant face vector search | M5 | ✅ PASS |
| Ollama embedding (mxbai-1024D) | M5 | ✅ PASS |
| Semantic search (LLM) | 皆未測試 | ❌ |

View File

@@ -0,0 +1,102 @@
# 向量數據存放狀況報告
**作者**M4
**日期**2026-05-06
---
## 1. 向量儲存方案總覽
Momentry 使用三種向量儲存方案:
| 方案 | 用途 | 維度 |
|------|------|------|
| **Qdrant** | 即時向量搜尋face / voice / visual chunk | face=512, voice=192, chunk=1024 |
| **pgvector** (PG extension) | 持久化向量儲存、identity 比對 | 多種維度 |
| **chunk_vectors** (PG table) | chunk embedding 快取 | 768 (nomic) / 1024 (mxbai) |
---
## 2. Qdrant即時向量搜尋
| Collection | M4 | M5 |
|-----------|-----|------|
| **Qdrant 服務** | ❌ 未執行 | ✅ port 6333 |
| `momentry_dev_face` | — | ✅ 64 points (512D) |
| `momentry_dev_voice` | — | ❌ **不存在** ← pipeline 失敗根因 |
| 其他 collection | 無 | 無 |
---
## 3. pgvectorPostgreSQL 內建向量)
### 3.1 向量欄位一覽
| Schema | Table | Column | Dim | M4 rows | M5 rows |
|--------|-------|--------|-----|---------|---------|
| dev | face_detections | embedding | **512** | **12,397** (100%) | **12,397** (100%) |
| dev | identities | embedding | ? | 41 (0 filled) | 41 (0 filled) |
| dev | identities | face_embedding | ? | 41 (12 filled) | 41 (12 filled) |
| dev | identities | voice_embedding | ? | 41 (0 filled) | 41 (0 filled) |
| dev | identities | identity_embedding | ? | 41 (0 filled) | 41 (0 filled) |
| dev | chunks | embedding | ? | 10,546 (**0%**) | 0 |
| public | face_detections | embedding | ? | — | ? |
| public | face_clusters | centroid | ? | — | ? |
| public | face_identities | embedding | ? | — | ? |
| public | identities | (4 cols) | ? | ? | ? |
| public | talents | face_embedding | ? | — | ? |
| public | talents | voice_embedding | ? | — | ? |
### 3.2 重點發現
#### ✅ face_detections最完整
- M4 + M5 各 **12,397 筆**,全部有 512D embedding
- 覆蓋 **2,347 個 trace** / **2 個 file_uuid**
- 這是目前最完整的向量資料集
#### ❌ chunks.embedding完全空白
- M4**10,546 筆 chunks0 筆有 embedding** (0%)
- M5chunks 為 0Rule 1 未觸發)
- chunk 向量從未被寫入 pgvector
#### ⚠️ identities 向量(部分空白)
- 41 個 identity**12 個有 face_embedding**TMDb 產生)
- **0 個有 voice_embedding 或 identity_embedding**
---
## 4. chunk_vectorsPG 傳統 table
| 項目 | M4 | M5 |
|------|-----|-----|
| Table | `public.chunk_vectors` | ❌ 不存在 |
| Total rows | 1,870 | — |
| Embedding dim | **3 ~ 768**(混雜) | — |
| 說明 | test123 (3D) + nomic-embed-text (768D) | M5 未建立 |
---
## 5. 綜合問題
### 🔴 Critical
1. **Qdrant `momentry_dev_voice` 不存在** — ASRX panicpipeline 阻塞
2. **chunks.embedding 完全空白** — Rule 1 chunks 無向量semantic search 無資料可查
### 🟡 Medium
3. **M4 Qdrant 未執行** — M4 無法做任何 vector search
4. **vector 維度不一致** — 現有 768D (nomic) vs 目標 1024D (mxbai)
5. **chunk_vectors table 位置混亂** — M4 在 public, M5 不存在
### 🟢 Low
6. **identities 向量不完整** — voice_embedding / identity_embedding 完全空白
7. **Qdrant face collection 資料異常** — 64 points 但 vectors_count=0
---
## 6. 建議順序
1. 建立 Qdrant `momentry_dev_voice` collection (192D) — 解 pipeline 阻塞
2. 用 mxbai-1024D 為 chunks 產生 embedding寫入 `dev.chunks.embedding`
3. M4 啟動 Qdrant
4. 重建 Qdrant face collection (512D) 並 sync
5. 清理 `public.chunk_vectors` 中 3D 測試資料

View File

@@ -0,0 +1,78 @@
# M4 / M5 Pipeline 分工計畫
**作者**M4
**日期**2026-05-07
---
## 分工原則
| 資源 | M4 (Mac Mini) | M5 (MacBook Pro) |
|------|---------------|-------------------|
| RAM | 16GB | **48GB** |
| CPU | 10 cores | **18 cores** |
| ANE latency | 66ms | **8.5ms (7.8x faster)** |
| Gemma4 LLM | ❌ | ✅ @ 8081 |
| Disk | 1.8TB | 1.8TB |
M5 所有資源均優於 M4因此**重處理在 M5輕服務在 M4**。
---
## 分工表
| 工作 | 由誰執行 | 原因 |
|------|---------|------|
| **長片 Pipeline** (ASR/ASRX/Face/YOLO/OCR/Pose/Cut) | **M5** | RAM 48GB 足夠 ASRANE 快 7.8x |
| **5W1H LLM Summary** | **M5** | Gemma4 @ 8081 在 M5 |
| **Text Embedding (ANE CoreML)** | **M5** | 8.5ms vs M4 66ms |
| **Dev Playground (3003)** | **M4** | 測試用 |
| **Production (3002)** | **M4** | 正式 release |
| **Portal (1420)** | **M4** | 前端開發測試 |
| **測試與監控** | **M4** | 測試者角色 |
---
## 資料流
```
M5 (重處理)
├── YouTube Charade Pipeline
│ ├── ASR (faster-whisper) → 457 segments
│ ├── ASRX (speaker diarization) → voice vectors → Qdrant
│ ├── Face (swift_face + ANE FaceNet) → face.json → face_detections
│ ├── YOLO / OCR / Pose / Cut
│ ├── Rule 1 Chunking → sentence chunks
│ ├── Rule 3 Scene Chunking → scene chunks
│ ├── Face Trace + DB Store
│ └── Qdrant Sync (face + voice + rule1)
├── 5W1H LLM (Gemma4 31B)
│ └── Scene summaries
└── ANE Embedding (CoreML .mlpackage, 8.5ms)
└── Text embedding for search
M4 (輕服務)
├── Dev Playground (3003)
├── Qdrant (voice+face+rule1 collections)
├── Portal (1420)
└── API 測試與驗證
```
---
## 實作方式
1. **M5** 處理 YouTube Charade 長片(已 sync 到 M5 demo path
2. **M5** pipeline 完成後結果DB + Qdrant + output JSONsync 回 **M4**
3. **M4** 用 sync 回來的資料進行搜尋 / 5W1H / API 驗證
---
## 優勢
- ASR 在 M5 48GB RAM 上不會 OOM
- Face embedding 在 M5 ANE 快 7.8 倍
- Gemma4 5W1H 直接在 M5 執行,不需網路延遲
- M4 維持乾淨的測試環境

View File

@@ -0,0 +1,45 @@
# M4 Pipeline 失敗原因與解法
**作者**M4
**日期**2026-05-07
---
## 現象
Job 140 中 ASR + YOLO 失敗原因記錄為「Worker restarted」。
## 根因分析
### 1. 記憶體壓力
M4 只有 16GB RAM且 swap 已使用 2.76GB / 4GB69%)。多個 processor 同時執行時:
| Processor | 約需 RAM |
|-----------|---------|
| ASR (faster-whisper) | 2-4GB |
| YOLO | 1-2GB |
| Face (swift_face) | ~500MB |
| OCR | ~500MB |
| Pose | ~500MB |
| **總計max_concurrent=2** | **~4-8GB** |
記憶體不足時OS 可能 kill worker process導致「Worker restarted」。
### 2. 非必要在 M4 執行
分工原則已確定M5 負責重處理48GB RAM, 18 coresM4 負責輕服務 + 測試。
## 解法
| 方案 | 說明 | 難度 |
|------|------|------|
| **A. 降低 M4 concurrency** | `max_concurrent=1`,一次只跑一個 processor | 🟢 簡單 |
| **B. M4 只跑輕量 processor** | 跳過 ASR只跑 cut/face/ocr/pose/yolo | 🟢 簡單 |
| **C. 接受分工** | M4 不做長片 pipeline只做測試驗證 | 🟢 最佳 |
## 建議
採用 **方案 C** — M4 pipeline 失敗是預期行為M4 完全不適合跑全片 ASR。M5 已正確執行 Job 255完成後將結果 sync 回 M4 驗證即可。
若 M4 需要測試 pipeline使用短影片short_clip.mov, 5s已有成功紀錄Job 137 completed

View File

@@ -0,0 +1,54 @@
# M5 提案Embedding 跨機器部署方案
## 背景
Portal search 需要 query-side embedding。之前 mxbai 是 English only已換成 EmbeddingGemma 300M768D, 多語)。討論後認為 M4 也需要能自己跑 embedding確保 M5 出門 demo 時 Portal 搜尋不受影響。
## 提案內容
### 分工
| 機器 | 角色 | embedding 來源 |
|------|------|---------------|
| M5 | 主力 server + pipeline 批量向量化 | Python MPS, port 11436 |
| M4 | Portal search + 離線備援 | 預設 call M5 API, M4 也裝一份 fallback |
### Portal 的 embed client
```javascript
async function embedQuery(text) {
const servers = [
'http://192.168.110.201:11436/v1/embeddings', // M5 主力
'http://localhost:11436/v1/embeddings', // M4 備援
];
for (const url of servers) {
try {
const res = await fetch(url, { ... });
return data.data[0].embedding;
} catch (e) { continue; }
}
throw new Error('Embedding servers unreachable');
}
```
### M4 安裝指令
```bash
pip install torch transformers flask
open https://huggingface.co/google/embeddinggemma-300m # 接受授權
huggingface-cli login --token YOUR_TOKEN
rsync -av accusys@192.168.110.201:/Users/accusys/momentry_core_0.1/scripts/embeddinggemma_server.py .
python3 embeddinggemma_server.py --port 11436
```
### 文件
詳細設計:`API_V1.0.0/DEPLOY/EMBEDDING_DEPLOYMENT_V1.0.0.md`
## 詢問 M4
1. Portal 團隊何時方便安裝 EmbeddingGemma
2. API fallback 邏輯要由 Portal 端實作,還是需要 M5 在後端做 proxy
3. M5 目前正在跑 Charade 的 5W1H+~5h完成後會自動 vectorize。Portal 需要等這個完成才能測試 search 嗎?
請 M4 回覆至 `M5_workspace/` 或直接更新此文件。

View File

@@ -0,0 +1,68 @@
# M5 近期變更 — 提供 M4 同步
**日期**2026-05-07
---
## 程式碼變更(需同步)
### 1. Identity Agent — 迭代多角度 face matching
**檔案**
- `src/core/tmdb/face_agent.rs` — 重寫 `match_faces_against_tmdb`,支援 iterative multi-angle propagation
- `src/api/identity_agent_api.rs``analyze_identity` 改為會寫入 DB + `match_faces_iterative` 函數
- 新增 `quality_check_temporal_collisions`:時序碰撞 QC
**效果**TMDb seed → 99% trace 綁定2769 traces 中 2759 matched
### 2. Scene Classification — ANE CoreML
**檔案**
- `scripts/scene_classifier.py` — CoreML predict 改為 numpy array 輸入 + softmax
- `src/core/processor/scene_classification.rs` — 傳入 CoreML model path
- 已轉換 CoreML 模型:`/Users/accusys/models/resnet18_places365.mlpackage`23MB
- ANE 延遲0.4msCPU 12.4ms28x 加速)
### 3. Places365 模型
- 下載安裝:`/Users/accusys/models/resnet18_places365.pth.tar`MIT 授權,可商用)
### 4. Pipeline 優化
- `src/worker/processor.rs``sweep_stale` 改為 reset 到 Pending非 Failed
- `src/worker/processor.rs``kill_existing_processor` 防止重複啟動
- `src/worker/job_worker.rs``any_pending` + `any_skipped` 檢查,防止 premature completion
- `src/api/server.rs` — 預設 processor 加入 scene
- `src/core/processor/executor.rs` — timeout 時保留 partial 輸出(.tmp → .json 非 .err
### 5. 5W1H+ Prompt
**檔案**`src/api/five_w1h_agent_api.rs` — 重寫parent 5W1H+ → child enhanced sentences
### 6. Embedding ANE CoreML
- CoreML 模型:`/Users/accusys/models/mxbai-embed-large-v1.mlpackage`669MB
- ANE 延遲8.5msOllama 14.6ms1.8x 加速)
- 環境變數:`MOMENTRY_EMBED_URL=http://localhost:11435`
## 環境變數 `.env.development` 新增
```
MOMENTRY_EMBED_URL=http://localhost:11435
MOMENTRY_LLM_SUMMARY_URL=http://192.168.110.201:8081/v1/chat/completions
MOMENTRY_LLM_SUMMARY_MODEL=google_gemma-4-26B-A4B-it-Q5_K_M.gguf
```
## 已下載的模型
| 模型 | 位置 | 用途 |
|------|------|------|
| Gemma4 26B MoE | `/Users/accusys/models/google_gemma-4-26B-A4B-it-Q5_K_M.gguf`18GB | 5W1H LLM |
| Qwen3 30B MoE | `/Users/accusys/models/Qwen_Qwen3-30B-A3B-Instruct-2507-Q5_K_M.gguf`20GB | 備用 LLM |
| Mistral 24B | `/Users/accusys/models/Mistral-Small-3.1-24B-Instruct-2503-Q5_K_M.gguf`16GB | 備用 LLM |
| mxbai-embed-large CoreML | `/Users/accusys/models/mxbai-embed-large-v1.mlpackage`669MB | ANE embedding |
| ResNet18 Places365 CoreML | `/Users/accusys/models/resnet18_places365.mlpackage`23MB | ANE scene class |
| bge-m3 GGUF | `/Users/accusys/models/bge-m3-q8_0.gguf`605MB | 備用 embedding |
| mxbai-embed-large GGUF | `/Users/accusys/models/mxbai-embed-large-v1-q8_0.gguf`341MB | 備用 embedding |
| nomic-embed-text-v2-moe GGUF | `/Users/accusys/models/nomic-embed-text-v2-moe.Q5_K_M.gguf`354MB | 備用 embedding |
## 背景任務執行中
1. **Face re-scan**:對 482 個 cut scenes 以 1-frame interval 重新掃描臉部,補充 single-frame traces
2. **Scene classification**:以 ANE CoreML + 5s 間隔,對 Charade 進行場景分類
## 已知問題
- Scene classification 輸出只有 1 個 sceneclass 129: door需要進一步調試 merge 邏輯

View File

@@ -0,0 +1,61 @@
# ANE Embedding 設定變更 — 請同步
**變更時間**2026-05-07
**影響版本**:需要更新 code + 設定
---
## 1. 程式碼變更
**檔案**`src/core/embedding/comic_embed.rs`
`Embedder::new()` 現在會讀取 `MOMENTRY_EMBED_URL` 環境變數決定 embedding server 位址:
```rust
// 預設 Ollama: http://localhost:11434
// 設定後切換到 ANE: http://localhost:11435
fn default_url() -> String {
std::env::var("MOMENTRY_EMBED_URL")
.unwrap_or_else(|_| "http://localhost:11434".to_string())
}
```
## 2. 新增檔案
`scripts/coreml_embed_server.py` — CoreML ANE embedding HTTP server
## 3. 環境變數
`.env.development` 新增一行:
```
MOMENTRY_EMBED_URL=http://localhost:11435
```
## 同步步驟
```bash
# 1. git pull (如有版控) 或 rsync 更新 code
# 2. 在自己的機器上啟動 ANE embedding server
python3 scripts/coreml_embed_server.py --port 11435 &
# 3. 確認 `.env.development` 有設定
grep MOMENTRY_EMBED_URL .env.development
# 4. 重啟 momentry_playground
# 如果之前已轉換好 .mlpackageANE server 會自動載入
```
## 轉換 CoreML 模型
如果還沒轉換,執行 `convert_embed_to_coreml.py`(參見 `ane_embedding_install_guide.md`)。
已轉好的模型也可從 M5 rsync
```bash
rsync -av accusys@192.168.110.201:/Users/accusys/models/mxbai-embed-large-v1.mlpackage /Users/accusys/momentry/
```
## 退回 Ollama
要臨時切回 Ollama註解掉 `.env.development``MOMENTRY_EMBED_URL` 即可,不需改 code。

View File

@@ -0,0 +1,122 @@
# ANE Embedding 安裝指南(給 M4
## 前置需求
```bash
# 1. 確認 Python venv 有 coremltools + transformers
pip install coremltools transformers torch numpy
```
## 轉換腳本
將以下內容存為 `convert_embed_to_coreml.py` 並執行:
```python
import os, numpy as np
from pathlib import Path
import torch
from transformers import AutoModel, AutoTokenizer
import coremltools as ct
MLPACKAGE_PATH = "/Users/accusys/momentry/mxbai-embed-large-v1.mlpackage"
print("Loading mxbai-embed-large-v1...")
model_name = "mixedbread-ai/mxbai-embed-large-v1"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModel.from_pretrained(model_name)
print(f"Model: BERT {model.config.hidden_size}D, {model.config.num_hidden_layers} layers")
model.eval()
class EmbeddingWrapper(torch.nn.Module):
def __init__(self, model):
super().__init__()
self.model = model
def forward(self, input_ids, attention_mask):
outputs = self.model(input_ids=input_ids, attention_mask=attention_mask)
token_emb = outputs.last_hidden_state
mask = attention_mask.unsqueeze(-1).expand(token_emb.size()).float()
return torch.sum(token_emb * mask, 1) / torch.clamp(mask.sum(1), min=1e-9)
wrapper = EmbeddingWrapper(model).eval()
example = tokenizer("Hello world", return_tensors="pt", padding="max_length", truncation=True, max_length=512)
print("Converting to CoreML...")
traced = torch.jit.trace(wrapper, (example["input_ids"], example["attention_mask"]))
ct_model = ct.convert(
traced,
inputs=[
ct.TensorType(name="input_ids", shape=(1, 512), dtype=np.int32),
ct.TensorType(name="attention_mask", shape=(1, 512), dtype=np.int32),
],
outputs=[
ct.TensorType(name="embedding", dtype=np.float16),
],
minimum_deployment_target=ct.target.macOS14,
compute_precision=ct.precision.FLOAT16,
compute_units=ct.ComputeUnit.ALL,
)
ct_model.save(MLPACKAGE_PATH)
size_mb = sum(f.stat().st_size for f in Path(MLPACKAGE_PATH).rglob('*') if f.is_file()) / 1e6
print(f"Saved: {MLPACKAGE_PATH} ({size_mb:.1f} MB)")
print("Done!")
```
## 測試腳本
存為 `test_coreml_embed.py`
```python
import numpy as np, time
import coremltools as ct
from transformers import AutoTokenizer
MLPACKAGE_PATH = "/Users/accusys/momentry/mxbai-embed-large-v1.mlpackage"
mlmodel = ct.models.MLModel(MLPACKAGE_PATH, compute_units=ct.ComputeUnit.ALL)
tokenizer = AutoTokenizer.from_pretrained("mixedbread-ai/mxbai-embed-large-v1")
texts = {
"English": "Hello and welcome to the old time movie show.",
"Chinese": "歡迎收看老電影節目。",
}
for name, text in texts.items():
tokens = tokenizer(text, return_tensors="np", padding="max_length", truncation=True, max_length=512)
input_ids = tokens["input_ids"].astype(np.int32)
attention_mask = tokens["attention_mask"].astype(np.int32)
for _ in range(3): # warmup
mlmodel.predict({"input_ids": input_ids, "attention_mask": attention_mask})
times = []
for _ in range(10):
t0 = time.perf_counter()
result = mlmodel.predict({"input_ids": input_ids, "attention_mask": attention_mask})
t1 = time.perf_counter()
times.append((t1 - t0) * 1000)
avg = sum(times) / len(times)
emb = result["embedding"]
print(f"{name:10s}: avg={avg:.1f}ms dim={len(emb[0])}")
print("\nDone! ANE embedding is working.")
```
## 執行
```bash
cd /Users/accusys/momentry_core_0.1
python3 convert_embed_to_coreml.py # 第一次轉換(~30秒
python3 test_coreml_embed.py # 測試 ANE 效能
```
## 預期結果M4 Mac Mini
| 項目 | 預期 |
|------|------|
| 轉換時間 | ~30秒 |
| 模型大小 | ~670MB |
| ANE 延遲 | ~10-15msM4 晶片 ANE 略慢於 M5 Max |
| Ollama 延遲 | ~15-25ms |
| 中文支援 | ✅ |

View File

@@ -0,0 +1,155 @@
# ANE Embedding 整合測試計畫
**作者**M4
**日期**2026-05-07
**待測版本**M5 完成全面 text embedding ANE CoreML 整合後
---
## 1. 測試範圍
M5 正在將所有 text embedding 從 Ollama GPU 改為 CoreML ANE。本計畫驗證改動後的正确性與效能。
### 受影響的程式碼路徑
| 路徑 | 目前使用 | 改為 |
|------|---------|------|
| `search.rs``smart_search` | Ollama `POST /api/v1/search/smart` | CoreML ANE |
| `universal_search.rs` → chunk search | Ollama `POST /api/v1/search/universal` | CoreML ANE |
| 任何其他 text embedding 呼叫 | Ollama | CoreML ANE |
---
## 2. 測試案例
### TC-1Embedding 正確性
**目的**:確認 ANE CoreML embedding 輸出與 Ollama 一致
**方法**
1. 對同一段 text分別用 Ollama 和 CoreML ANE 產生 embedding
2. 計算 cosine similarity
**預期**similarity > 0.99(兩位小數精度內一致)
**測試用 text**
```
"Hello and welcome to the old time movie show"
"Cary Grant and Audrey Hepburn star in Charade"
"歡迎收看老電影節目"
"感謝您的觀看"
```
**指令範例**
```bash
# Ollama
curl -s http://localhost:11434/api/embeddings \
-H "Content-Type: application/json" \
-d '{"model":"mxbai-embed-large","prompt":"Hello world"}' | jq '.embedding[:5]'
# CoreML ANE透過 Python
python3.11 -c "
import coremltools as ct
from transformers import AutoTokenizer
m = ct.models.MLModel('models/mxbai-embed-large-v1.mlpackage')
t = AutoTokenizer.from_pretrained('mixedbread-ai/mxbai-embed-large-v1')
tok = t('Hello world', return_tensors='np', padding='max_length', truncation=True, max_length=512)
r = m.predict({'input_ids': tok['input_ids'].astype(np.float32), 'attention_mask': tok['attention_mask'].astype(np.float32)})
print(list(r.values())[0][0,:5])
"
```
### TC-2搜尋結果一致性
**目的**:確認改用 ANE 後search API 的回傳結果與原本一致或更好
**方法**
1. 對同一影片執行 `POST /api/v1/search/universal` 使用相同 query
2. 比較 ANE 版與 Ollama 版的 top-5 結果
**測試 queries**
| Query | 預期包含 |
|-------|---------|
| `"Cary Grant"` | 含有 Cary Grant 的 chunk |
| `"how do you shave"` | 對話原文 chunk |
| `"Audrey Hepburn"` | 含有 Audrey Hepburn 的 chunk |
| `"thank you"` | 含有 "thank you" 的對話 |
**指令範例**
```bash
curl -s -X POST http://localhost:3003/api/v1/search/universal \
-H "Content-Type: application/json" \
-d '{"query":"Cary Grant","uuid":"417a7e93860d70c87aee6c4c1b715d70","limit":5}' | jq '.results[] | {score, text: .text[:60]}'
```
### TC-3效能基準
**目的**:確認 ANE embedding latency
**方法**:對不同長度 text 各執行 10 次,取平均 latency
| Text 長度 | 內容範例 |
|-----------|---------|
| short (10B) | `"Hello world"` |
| medium (50B) | `"Cary Grant and Audrey Hepburn star in Charade."` |
| long (500B) | `"word " * 100` |
| 中文 short | `"你好世界"` |
| 中文 long | `"歡迎收看老電影節目 " * 20` |
**預期 latency**M4 Mac Mini
| 方案 | short | medium | long | 中文 |
|------|-------|--------|------|------|
| **CoreML ANE** | ~60ms | ~60ms | ~60ms | ~60ms |
| Ollama (基準) | ~50ms | ~80ms | ~150ms | ~100ms |
> ANE latency 理論上不隨 text length 變化(固定 padding 到 512 tokens
### TC-4搜尋 API 端點功能
**目的**:確認所有相關 API 端點在 ANE 整合後仍正常運作
| Endpoint | Method | 測試方式 |
|----------|--------|---------|
| `/api/v1/search/smart` | POST | 執行 query + 確認回傳 200 + 有 results |
| `/api/v1/search/universal` | POST | 執行 query + 確認回傳 200 + 有 results |
| `/api/v1/search/frames` | POST | 執行 query + 確認回傳 200 |
### TC-5邊界情況
| 測試 | 方法 | 預期 |
|------|------|------|
| 空字串 query | `{"query":""}` | 回傳 400 或空結果 |
| 超長 query | 5000+ chars | 正常截斷回傳 |
| 無 uuid | `{"query":"test"}` | 回傳 400uuid required |
| 不存在的 uuid | `{"query":"test","uuid":"xxx"}` | 回傳空結果或 404 |
---
## 3. 接受標準
| # | 標準 | 必須 |
|---|------|------|
| 1 | ANE embedding output 與 Ollama cosine similarity > 0.99 | ✅ |
| 2 | 搜尋結果 top-3 與改動前一致 | ✅ |
| 3 | API endpoint 回傳 200 + 有效 JSON | ✅ |
| 4 | ANE latency 不高於 Ollama latency | ✅ |
| 5 | 空字串 / 超長 query 不 crash | ✅ |
---
## 4. 測試環境
| 項目 | M4 | M5 |
|------|-----|-----|
| 角色 | 測試者 | 開發者 |
| Playground | port 3003 | port 3003 |
| CoreML ANE model | `models/mxbai-embed-large-v1.mlpackage` | ✅ |
| Ollama 基準 | `localhost:11434` | `192.168.110.201:11434` |
---
## 5. 注意事項
1. ANE 首次推論可能較慢model loading需 warmup
2. CoreML model 的 `compute_units` 需設為 `ComputeUnit.ALL` 以啟用 ANE
3. 若 ANE 不可用,應自動 fallback 到 CPU/GPUComputeUnit.ALL 會自動選擇)

View File

@@ -0,0 +1,27 @@
# ANE Embedding 測試成功
**測試者**M5
**日期**2026-05-07
---
## CoreML mxbai-embed-large-v1 轉換成功
`/Users/accusys/models/mxbai-embed-large-v1.mlpackage`669MB
## 效能M5 Max, 10 次平均)
| 方案 | 平均延遲 | 穩定度 | 中文支援 |
|------|---------|--------|---------|
| **CoreML ANE** | **8.5ms** | ±0.3ms ✅ | ✅ |
| Ollama (Metal) | 14.6ms | ±10ms | ✅ |
| **Speedup** | **~1.8x** | **更穩定** | **不影響** |
ANE embedding 比 Ollama 快約一倍且延遲非常穩定8.2-8.7ms vs 12-23ms
## 檔案位置
| 用途 | 路徑 |
|------|------|
| CoreML 模型 | `/Users/accusys/models/mxbai-embed-large-v1.mlpackage` |
| Ollama 模型 | `mxbai-embed-large:latest` (port 11434) |

View File

@@ -0,0 +1,35 @@
# Embedding 效能評估報告Ollama vs llama.cpp
**作者**M4
**日期**2026-05-07
**測試環境**M5 (192.168.110.201, Apple M5 Max, 48GB)
---
## 測試結果
| 方案 | Model | Dim | short (11B) | medium (46B) | long (1000B) | RAM |
|------|-------|-----|------------|-------------|-------------|-----|
| **llama.cpp** | bge-m3 (Q8_0, 605MB) | 1024 | **42ms** | **50ms** | **152ms** | ~1.2GB |
| **Ollama** | mxbai-embed-large (669MB) | 1024 | **50ms** | **46ms** | **145ms** | ~1.3GB |
---
## 結論:效能幾乎相同
兩者 latency 差異在 ±10ms 內,對實際使用無感。
## 建議:使用 Ollama
| 考量 | Ollama | llama.cpp |
|------|--------|-----------|
| 效能 | ✅ 略同 | ✅ 略同 |
| 設定複雜度 | ✅ 一行指令 | ❌ 需另開 server + port |
| 現有整合 | ✅ `comic_embed.rs` 已支援 | ❌ 需改 code |
| 模型管理 | ✅ `ollama pull` | ❌ 手動下載 GGUF |
| 多模型支援 | ✅ 多模型同服務 | ⚠️ 每個模型需不同 port |
| 5W1H LLM | ❌ 不適用 | ✅ 可用 Gemma4(8081) |
**結論**:維持 Ollama 做 embedding。Ollama 已整合進現有 codebase、模型管理方便效能與 llama.cpp 無顯著差異。bge-m3 可作為 multilingual 備選方案。
已在 M5 安裝好 llama.cpp bge-m3 embedding serverport 8082如需交叉驗證可隨時使用。

View File

@@ -0,0 +1,38 @@
# M4 Embedding Benchmark (含 ANE CoreML)
**作者**M4
**日期**2026-05-07
---
## 三種方案比較
| 方案 | Engine | Model | Dim | short 5B | long 1000B | 穩定度 | 硬體 |
|------|--------|-------|-----|----------|-----------|--------|------|
| **CoreML ANE** | mxbai-embed-large.mlpackage | mxbai-embed-large | **1024** | **69ms** | **63ms** | ✅ 不受 text length 影響 | **ANE** |
| Ollama GPU | ollama @ 11434 | mxbai-embed-large | 1024 | 50ms | 145ms | ⚠️ 隨 text length 增加 | Metal GPU |
| llama.cpp GPU | llama-server @ 8082 | bge-m3 Q8_0 | 1024 | 63ms | 220ms | ⚠️ 隨 text length 增加 | Metal GPU |
---
## 關鍵發現
**CoreML ANE 穩定度最高** — 因為模型固定 padding 到 512 tokens不論 text 長度 latency 都維持在 63-69ms。Ollama 和 llama.cpp 則隨 text length 線性增加。
---
## 建議
1. **Primary**: CoreML ANE (mxbai-embed-large.mlpackage) — 最穩定、最低功耗
2. **Fallback**: Ollama mxbai-embed-large — 已整合進 codebase
3. **備用**: llama.cpp bge-m3 — 多語言需求時使用
## CoreML 模型位置
```
/Users/accusys/momentry_core_0.1/models/mxbai-embed-large-v1.mlpackage
```
## 更新 search.rs
改為使用 CoreML ANE embedding 的變更由 M5 處理。

View File

@@ -0,0 +1,38 @@
# M4 Embedding 效能比較Ollama vs llama.cpp
**作者**M4
**日期**2026-05-07
**測試環境**M4 (Mac Mini, M4, 16GB, 10 cores)
---
## 測試結果
| 方案 | Model | Dim | short 11B | medium 40B | long 1000B |
|------|-------|-----|-----------|------------|-----------|
| **llama.cpp** | bge-m3 Q8_0 (605MB) | 1024 | **63ms** | **80ms** | **220ms** |
| Ollama | mxbai-embed-large (669MB) | 1024 | 678ms* / 77ms | **77ms** | 284ms |
| Ollama | bge-m3 (1.2GB) | 1024 | 1824ms* / 125ms | 125ms | 251ms |
> *第一次呼叫包含 model loading 時間
---
## 分析
1. **llama.cpp + bge-m3 最快**63-220ms且無首次 loading 延遲
2. **Ollama 首次 loading 慢**678ms~1.8s),但之後穩定於 77-284ms
3. **兩者效能接近**77-284ms vs 63-220ms差異在日常使用無感
## 建議
| 使用場景 | 建議方案 | 原因 |
|---------|---------|------|
| 單次 embedding如 search query | Ollamacurrent | 已有整合,無需改 code |
| 批次大量 embedding | llama.cpp | 無 model loading 開銷 |
| 多語言需求 | bge-m3Ollama 或 llama.cpp | 支援中英日等多語 |
| 一般英文 | mxbai-embed-largeOllama | 品質穩定,現行預設 |
**結論**:維持 Ollama 為主要 embedding 方案。llama.cpp bge-m3 作為備用/批次處理方案。
M4 的 llama.cpp bge-m3 embedding server 已在 port 8082 運作中。

View File

@@ -0,0 +1,16 @@
# M5 Embedding Models
**提供者**M5
**日期**2026-05-07
詳見 M5 workspace
`docs_v1.0/M5_workspace/2026-05-07_embedding_models_location.md`
## 簡要
| 模型 | 位置 / API | 維度 |
|------|-----------|------|
| mxbai-embed-large (Ollama) | `192.168.110.201:11434` | 1024D |
| bge-m3 (Ollama) | `192.168.110.201:11434` | 1024D |
| nomic-embed-text-v2-moe (Ollama) | `192.168.110.201:11434` | 768D |
| GGUF files | `/Users/accusys/models/*.gguf` | — |

View File

@@ -0,0 +1,113 @@
# 檔案歷程打包機制設計討論
**作者**M4
**日期**2026-05-07
---
## 目標
將一支檔案從註冊到處理完成的所有產出打包成一個可攜帶的封包,可在另一台 Momentry 實例上還原,不需重新處理。
---
## 打包內容
一個檔案在 Momentry 中的完整歷程包含:
| 類別 | 內容 | 來源 | 大小估計 |
|------|------|------|---------|
| **原始檔** | 影片/照片原始檔案 | `videos.file_path` | 596MB~2.2GB |
| **Metadata** | probe JSON, duration, fps, resolution | `videos` table | < 1MB |
| **Output JSON** | face.json, asr.json, cut.json, yolo.json 等 | `output_dev/` directory | 1MB~100MB |
| **Processor results** | 狀態、時間、版本、產出量 | `processor_results` table | < 1MB |
| **Pre-chunks** | 各 processor 原始 frame 級資料 | `pre_chunks` table | 10MB~500MB |
| **Chunks** | sentence chunks, scene chunks, story chunks | `chunks` table | 1MB~50MB |
| **Face detections** | 人臉追蹤結果 + embeddings | `face_detections` table | 5MB~50MB |
| **Qdrant vectors** | face, voice, rule1 向量 | Qdrant collections | 10MB~200MB |
| **Identities** | 人物身份綁定 | `identities` + `identity_bindings` | < 1MB |
| **5W1H summaries** | LLM 產生的場景摘要 | `chunks.summary_text` | < 1MB |
---
## 打包格式提案
```
file_uuid.tar.gz
├── metadata.json # videos table row + probe_json
├── output/ # output JSON 檔案
│ ├── face.json
│ ├── asr.json
│ ├── cut.json
│ └── ...
├── data/ # DB 資料JSON 格式)
│ ├── processor_results.json
│ ├── pre_chunks.json
│ ├── chunks.json
│ ├── face_detections.json
│ └── identities.json
├── vectors/ # Qdrant 向量NPZ 或 JSON
│ ├── face_vectors.npz
│ ├── voice_vectors.npz
│ └── rule1_vectors.npz
├── manifest.json # 版本資訊 + checksum
└── original/ # 原始檔案(可選)
└── video.mp4
```
## 關鍵設計問題
| 問題 | 討論 |
|------|------|
| **向量如何打包?** | Qdrant 無原生匯出 API。需從 PG `face_detections.embedding` 讀取,或直接 query Qdrant |
| **UUID 衝突?** | 還原時 file_uuid 可能與目標系統重複。需支援 UUID remapping |
| **原始檔超大?** | 可選是否包含原始檔。不含時需在新系統重新註冊同一路徑 |
| **Identity 整合?** | 打包中的 identity 是否要 merge 到目標系統的 identity 池? |
| **PG 與 Qdrant 一致性?** | 向量同時存在 PG 和 Qdrant需確保兩者同步 |
---
## 使用場景
```
場景 AM5 處理 → 打包 → M4 匯入驗證
M5 跑完 Charade pipeline
→ momentry export aeed71342a89 --output charade.tar.gz
→ scp charade.tar.gz M4
→ momentry import charade.tar.gz
→ M4 可直接搜尋、查 face、看 chunks不需重跑
場景 B開發環境 → 正式環境
dev playground → export → import → production
場景 C備份歸檔
已完成處理的檔案 → export → 儲存 → 日後可還原查詢
```
---
## API 設計草案
```bash
# 匯出
POST /api/v1/file/{uuid}/export
→ 回傳下載 URL 或直接下載 tar.gz
# 匯入
POST /api/v1/files/import
Content-Type: multipart/form-data
file=@charade.tar.gz
→ 回還原後的 file_uuid
```
---
## 優先級
| 項目 | 優先級 | 說明 |
|------|--------|------|
| Output JSON 打包 | 🟢 Easy | 直接 tar output_dev 中該 uuid 的檔案 |
| DB data 匯出 | 🟡 Medium | 需 pg_dump 或 JSON serialization |
| Qdrant 向量匯出 | 🔴 Hard | 需逐條讀取 Qdrant points |
| 匯入還原 | 🔴 Hard | 需處理 UUID remapping + identity merge |
| 含原始檔 | 🟡 Medium | 大檔案傳輸,可選 |

View File

@@ -0,0 +1,93 @@
# PDF 文件處理討論
**作者**M4
**日期**2026-05-07
---
## 問題
目前 Momentry 無法處理 PDF
- ffprobe 無法解析 PDF回傳錯誤
- 無對應 processor 腳本
- 無 text extraction 工具安裝
---
## 方案提案
### 核心想法PDF = 多頁圖片,每頁 = 1 幀 video
```
PDF (10頁)
├── 頁 1 → page_001.jpg ─→ OCR + YOLO + Face如同單幀 video
├── 頁 2 → page_002.jpg ─→ OCR + YOLO + Face
├── ...
└── 頁 10 → page_010.jpg ─→ OCR + YOLO + Face
+ pdftotext → raw text → BM25 search
```
### 需要安裝的工具
| 工具 | 用途 | 安裝方式 |
|------|------|---------|
| `poppler`pdftoppm, pdftotext | PDF→圖片、PDF→文字 | `brew install poppler` |
| `tesseract`(選配) | 圖片 OCR目前已用 PaddleOCR | `brew install tesseract` |
---
## 設計選項
### A. 新增 `pdf_processor.py`
專用 processor處理 PDF 的完整流程:
```
pdf_processor.py
1. pdftoppm → page_001.jpg ... page_N.jpg每頁一張圖
2. pdftotext → raw_text.txt直接提取文字
3. (選) tesseract → 對圖片 OCR若 pdftotext 效果差)
4. 輸出 JSON每頁結果 + 全文
```
**優點**:獨立、專注、可慢慢優化
**缺點**:無法複用現有 YOLO/Face/Ocr processors
### B. 註冊 PDF → 拆成多張單幀 video → 分別註冊
將 PDF 每頁轉為 JPEG分別註冊為單幀 video
```
PDF
→ pdftoppm → page_001.jpg (註冊為單幀 video)
page_002.jpg (註冊為單幀 video)
...
每個 page_N.jpg 可跑 face / yolo / ocr現有 processor 不需改)
+ pdftotext 產出全文 chunk獨立進 search
```
**優點**:完全複用現有 processorYOLO/Face/OCR 對單幀 video 已可用)
**缺點**:多個 UUID管理複雜
### C. 混合方案(建議 🏆)
```
PDF → PDF Processor (新增)
├── pdftotext → text → chunk → BM25 search
└── pdftoppm → page images
└── 對每頁執行現有OCR (PaddleOCR) + YOLO + Face
└── 結果彙整到同一個 file_uuid
```
## 待討論
| 項目 | 問題 |
|------|------|
| 註冊方式 | PDF 用獨立 `POST /api/v1/documents/register` 還是共用 `files/register` |
| 資料表 | 沿用 `videos` table 還是新增 `documents` table |
| Processor dependency | YOLO/Face 對文件頁面是否有意義? |
| PDF 文字搜尋 | 純文字 BM25 search簡單vs 完整 embedding search複雜 |
| 優先級 | 這是現在要做的事還是 roadmap 項目? |

View File

@@ -0,0 +1,147 @@
# Pipeline 問題分析報告
**作者**M4
**日期**2026-05-07
**測試標的**YouTube Charade (25fps, AV1, English) UUID: `aeed71342a899fe4b4c57b7d41bcb692`
---
## 總覽
7/7 processors completedjob 標為 completed但部分 post-processing 產出為 0。
---
## 問題 1ASR 輸出格式與 Rule 1 不匹配 🔴
### 現象
- ASR processor completed457 segments
- Rule 1 Ingestion completed: **0 chunks inserted**
- Qdrant `momentry_dev_rule1` 卻有 1,630 points
### 根因
ASR 輸出使用 `segments` 格式:
```json
{
"language": "en",
"segments": [
{"start": 197.04, "end": 199.04, "text": "You", "scene_number": 25, "language": "en"},
...
]
}
```
但 Rule 1 ingest (`rule1_ingest.rs`) 預期 `frames` / `faces` 格式:
```rust
// rule1_ingest.rs 中的 fetch_asr_segments() 查詢
// 從 pre_chunks 讀取processor_type='asr'
// 期待 data 中有 text, start, end 等欄位
```
ASR 的 segments 格式雖然正確寫入了 pre_chunksdata 欄位包含 text/start/end但 Rule 1 的解析邏輯可能與 segments 結構不完全相容。
### 影響
- 無法產生 sentence chunks → Rule 3 無內容可聚合 → 5W1H summary 無法執行
- Qdrant `momentry_dev_rule1` 有 1,630 points可能從其他地方來
---
## 問題 2ASR 輸出寫入 .err 檔案 🟡
### 現象
```bash
ls output/*.asr.json # ❌ 不存在
ls output/*.asr.json.err # ✅ 55KB內含完整 ASR JSON
```
### 根因
ASR processor 將 stdout 寫到 `.asr.json`stderr 寫到 `.asr.json.err`。但 ASR script 將 JSON 輸出寫往了 stderr 而非 stdout。
### 影響
- 若不手動複製 `.err → .json`,後續 processor 找不到 ASR 輸出
- 已在 M4 手動修正後 pipeline 才順利跑完
---
## 問題 3face_detections = 0 🟡
### 現象
- face.json3,993 frames, 6,188 face detectionssample_interval=30
- Face trace + DB store✅ completed
- face_detections table**0 筆**
### 根因推測
1. **sample_interval=30** 對 25fps 影片 = 每 1.2 秒一幀。相鄰 frame 間隔過大IoU + embedding matching 無法建立 trace
2.`store_traced_faces.py` 執行時找不到 `scene.json``cut.json`(因 registration 時 cut 失敗)
3. 或 scene-cut 邏輯將所有 face 都 reset 掉1330+ scenes
### 影響
- 無法做 trace-based identity binding
- Qdrant face collection 有 5,831 pointsprocessor 直接寫入的),但沒有 trace_id
---
## 問題 4Registration 階段 cut_processor 參數錯誤 🟡
### 現象
```
[REGISTER] CUT failed: cut_processor.py: error: unrecognized arguments: --threshold 27
```
### 根因
`src/api/server.rs``register_single_file()` 中傳遞 `--threshold 27``cut_processor.py`,但該 script 已更新,不再支援此參數。
### 影響
- Registration 時 CUT/scene detection 失敗
- 但 pipeline 中的 CUT processor 可以單獨執行成功(不傳 threshold
- 需要同步 server.rs 與 cut_processor.py 的介面
---
## 問題 5search/universal Chunk 缺少 fps 🔴
**檔案**`src/api/universal_search.rs`
`SearchResult::Chunk``start_frame``end_frame`,但**沒有 `fps`**。前端無法將 frame number 換算為時間。
---
## 問題 6IdentityChunkItem 缺少 frame 欄位 🟡
**檔案**`src/api/identity_api.rs`
`IdentityChunkItem` 只有 `start_time``end_time`,缺 `start_frame``end_frame``fps`
---
## 問題 7多個 search endpoint 缺少 frame 欄位文件已補code 未修)
詳見 `M5_workspace/2026-05-06_bug_search_missing_fps.md`
---
## 癥結圖
```
ASR (.err→.json 問題)
├── segments format → Rule 1 ingest 無法解析 → 0 sentence chunks
│ │
│ Rule 3 無內容可聚合
│ │
│ 5W1H summary 無法執行
└── 手動複製 .err → .json 後可讀
但格式仍是 segmentsRule 1 仍無法產出 sentence chunks
```
## 建議優先順序
| # | 問題 | 優先級 | 影響 |
|---|------|--------|------|
| 1 | Rule 1 支援 ASR segments 格式 | 🔴 High | 5W1H pipeline 阻塞 |
| 2 | ASR 輸出寫入正確檔案路徑 | 🔴 High | 每次需手動修正 |
| 3 | cut_processor --threshold 參數 | 🟡 Medium | Registration 時 CUT 失敗 |
| 4 | face tracker scene-cut + 稀疏取樣 | 🟡 Medium | face_detections 0 |
| 5 | universal search Chunk 缺 fps | 🟡 Medium | 前端 frame 定位 |
| 6 | IdentityChunkItem 缺 frame 欄位 | 🟢 Low | 前端顯示 |

View File

@@ -0,0 +1,73 @@
# Pipeline 進度報表標準格式
**版本**v2
**日期**2026-05-07
**提供者**M5
---
## 報表範本
```
=== Job {id} 完整報表 (frame總量: {total_frames}) ===
── Processors ──
Proc St Start End 已產出 已處理
------ ---- ----- ----- -------------- ----------
cut ✅ 04:28 04:43 2,260 scenes 169625
face ✅ 04:29 05:05 1,121 frames 169625
ocr ✅ 04:29 04:51 1,212 frames 169625
pose ✅ 04:29 04:40 4,211 frames 169625
yolo ⏳ 04:28 - 7,852 frames 6,803
asr ⏳ 04:28 - 148 segments 17,969
asrx ⬜ - - - -
已處理 4/7
── Post-Processing ──
Stage Status 已產出 依賴進度狀態
------------------- ---------- -------------- ----------
Rule 1 chunks ⬜ - ASR⏳ + ASRX⬜
ANE vectorize ⬜ 0 Rule 1 chunks⬜
Rule 3 scenes ⬜ - all 7 processors⬜
face_trace ⬜ - all 7 processors⬜
Qdrant face sync ⬜ 0 points face_trace⬜
TMDb face match ⬜ 0 face_trace⬜
Identity Agent ⬜ - face_trace✅ + ASRX✅
5W1H Agent ⬜ - Rule 1✅ + Rule 3✅
```
## 欄位說明
### Processors 表
| 欄位 | 說明 |
|------|------|
| Proc | Processor 名稱cut, face, ocr, pose, yolo, asr, asrx |
| St | ✅ completed / ⏳ running / ⬜ pending |
| Start | 開始時間HH:MM |
| End | 完成時間HH:MMrunning 中顯示 - |
| 已產出 | 該 processor 產出的資料量scenes/frames/segments |
| 已處理 | 以 frame 為單位的處理進度running 中顯示當前 frame |
### Post-Processing 表
| 階段 | 觸發時機 | 依賴進度狀態 |
|------|---------|-------------|
| Rule 1 chunks | ASR + ASRX 皆 ✅ | 顯示當前 ASR 與 ASRX 的即時狀態 |
| ANE vectorize | Rule 1 chunks 完成後 | 顯示 Rule 1 狀態 |
| Rule 3 scenes | 全部 7 個 processor 皆 ✅ | 顯示每個 processor 的即時完成狀態 |
| face_trace | 全部 7 個 processor 皆 ✅ | 同 Rule 3 |
| Qdrant face sync | face_trace 完成後 | 顯示 face_trace 狀態 |
| TMDb face match | face_trace 完成後 + TMDb enabled | 顯示 face_trace 狀態 |
| Identity Agent | face_trace + ASRX 皆 ✅ | 顯示 face_trace 與 ASRX 的即時狀態 |
| 5W1H Agent | Rule 1 + Rule 3 皆 ✅ | 顯示 Rule 1 與 Rule 3 狀態 |
## Status 標記
| 標記 | 意義 |
|------|------|
| ✅ completed | 已完成 |
| ⏳ running | 執行中 |
| ⬜ pending | 等待條件成立(條件欄位顯示 waiting for... |
| ❌ failed | 失敗 |
| ⏭️ skipped | 跳過(因依賴失敗) |

View File

@@ -0,0 +1,45 @@
# M4 回覆
**回覆者**M4
**日期**2026-05-07
---
## ASR JSON 樣本YouTube 25fps 測試)
```json
{
"language": "",
"segments": [
{"start": 197.04, "end": 199.04, "text": "You", "scene_number": 25, "language": "en"},
{"start": 212.64, "end": 213.64, "text": "Olá!", "scene_number": 32, "language": "pt"}
],
"total_segments": 457
}
```
格式為 `segments[]`(非舊的 `frames[]`),有 `scene_number`**無 `id`**
## Rule 1 0 chunks 根因確認
`dev.pre_chunks``processor_type='asr'` 的資料為 **0 筆**。因為 ASR 原始執行失敗(寫入 `.err`pre_chunks 從未被插入。即使事後複製 `.err→.json` + 標記 completed**pre_chunks 已遺失**。
這解釋了:
- Rule 1 抓不到 ASR segments → 0 sentence chunks
- 但 Qdrant `momentry_dev_rule1` 有 1,630 points來自其他 processor 的 vectorization
## ASR_TIMEOUT 分析
YouTube 版 113min @ 25fpsASR 約跑了 **30 分鐘後 timeout**`.json.tmp → .json.err`)。但實際已有 457 segments 產出(完整涵蓋前 ~30min 內容)。建議:
- **不增加 timeout**30min 已經太久)
- 改為 **streaming 模式**ASR 每完成一個 segment 就 flush不要等到全部完成才寫入
-**分段處理**:將長片切成 N 段分別 ASR再合併
## face_detections = 0
M4 YouTube 測試也有一樣問題6,188 face detections face.json0 face_detections。M5 Charade Job 251 也遇到。估計是 scene-cut reset 邏輯配合稀疏取樣sample_interval=30 @ 25fps導致。
## 待確認
M4 即將測試 face_tracker 對短片的行為(較小 interval確認是否為取樣密度問題。

View File

@@ -0,0 +1,84 @@
# 單幀 Video (Photo) 處理選型測試報告
**作者**M4
**日期**2026-05-07
---
## 測試目的
驗證將照片JPEG/PNG視為單幀 video 處理的可行性。
---
## 測試環境
| 項目 | 值 |
|------|-----|
| 測試機器 | M4 (Mac Mini) |
| Playground | port 3003 |
| 測試照片 | `animal.jpg` (1920x1280)、`people.jpg` (2048x3078) |
---
## 測試結果
### 1. 註冊Registration
| 格式 | 結果 | type | frames | fps |
|------|------|------|--------|-----|
| **JPEG** (.jpg) | ✅ 成功 | `video` | 1 | 25.0 |
| **PNG** (.png) | ✅ 成功 | `video` | 0 | 25.0 |
| MP4 短片 | ✅ 成功 | `video` | 475 | 60.0 |
JPEG 被 ffprobe 識別為 1 幀 mjpeg videoPNG 為 0 幀。
### 2. 處理器Processors on animal.jpg
| Processor | 結果 | 耗時 | 說明 |
|-----------|------|------|------|
| **YOLO** | ✅ **Success** | ~30s | 1 chunk, 1 frame — 完整運作 |
| **ASR** | ✅ **Success** | ~1s | 無音軌,快速回傳 empty |
| **CUT** | ✅ **Success** | ~1s | 1 幀無 scene cut |
| **Face** | ❌ Failed | — | `swift_face` 無法直接開啟 JPEG`Error: Cannot Open` |
| OCR | ❌ Failed | — | script 不支援圖片格式 |
| Pose | ❌ Pending | — | 可能類似 Face 問題 |
| ASRX | ⏳ Pending | — | 依賴 ASR |
### 3. Face Detection — 解法驗證
**照片 → ffmpeg → 1 幀 mov → swift_face**
| 照片 | 格式轉換 | 結果 |
|------|---------|------|
| `animal.jpg` (無臉) | `ffmpeg -i animal.jpg -vframes 1 animal.mov` | 0 faces ✅ |
| `people.jpg` (有人) | 同上 | **1 face** ✅ (bbox 761x743, conf 0.84) |
**關鍵發現**`swift_face` 使用 Apple Vision Framework不吃 JPEG 直接輸入,但吃單幀 MOV。轉換後人臉偵測正常運作。
---
## 結論
| 項目 | 支援度 | 處理方式 |
|------|--------|---------|
| **註冊** | ✅ 可直接註冊 | ffprobe 自動判定為 1 幀 video |
| **YOLO 偵測** | ✅ 可直接用 | 讀取 1 幀 image2 格式 |
| **ASR / CUT** | ✅ 自動跳過 | 無音軌/1 幀 → 空結果 |
| **Face 偵測** | ✅ **需轉換** | JPEG → ffmpeg → 1 幀 MOV → swift_face |
| **OCR** | ❌ 待確認 | script 需調整支援圖片 |
| **Pose** | ❌ 待確認 | 可能需類似 Face 的轉換 |
## 建議
Face processor 增加 JPEG/PNG 前置轉換:
```
if input is image file:
ffmpeg -i input.jpg -vframes 1 /tmp/single.mov
swift_face /tmp/single.mov ...
else:
swift_face input.mp4 ...
```
此修改可讓照片的人臉偵測、YOLO、縮圖全部無縫運作。

View File

@@ -0,0 +1,488 @@
# Momentry API 教材 — Marcom 團隊必備
**版本**v1.0
**適用**Marcom 團隊日常使用
**難度**:初階,附 curl 指令範例
---
## 目錄
1. [什麼是 Momentry API](#1-什麼是-momentry-api)
2. [環境準備](#2-環境準備)
3. [API Key 認證](#3-api-key-認證)
4. [常用功能 ①:註冊影片](#4-常用功能-①註冊影片)
5. [常用功能 ②:查詢影片列表](#5-常用功能-②查詢影片列表)
6. [常用功能 ③:查詢影片詳細資料](#6-常用功能-③查詢影片詳細資料)
7. [常用功能 ④:搜尋對話內容](#7-常用功能-④搜尋對話內容)
8. [常用功能 ⑤:搜尋人物](#8-常用功能-⑤搜尋人物)
9. [常用功能 ⑥:觀看影片](#9-常用功能-⑥觀看影片)
10. [常用功能 ⑦:取得影片截圖](#10-常用功能-⑦取得影片截圖)
11. [進階功能:觸發影片處理](#11-進階功能觸發影片處理)
12. [進階功能:查詢處理進度](#12-進階功能查詢處理進度)
13. [常見問題](#13-常見問題)
---
## 1. 什麼是 Momentry API
Momentry API 是一個 HTTP-based 的影片分析服務。你上傳一支影片,系統會自動:
- 🎬 **偵測場景變化** — 找出影片中每一幕的切換點
- 🗣️ **語音轉文字 (ASR)** — 把對話轉成可搜尋的文字
- 👤 **人臉辨識** — 辨識影片中出現的人物
- 🔍 **全文搜尋** — 用關鍵字搜尋對話內容
### 基本概念
| 名詞 | 說明 | 舉例 |
|------|------|------|
| **file_uuid** | 每支影片的唯一識別碼 | `aeed71342a899fe4b4c57b7d41bcb692` |
| **chunk** | 影片中的一段(對話、場景) | `"Hello and welcome..."` |
| **trace_id** | 同一個人臉的追蹤編號 | `trace_0`, `trace_1` |
| **identity** | 人物身份 | Cary Grant, Audrey Hepburn |
| **API Key** | 存取 API 的鑰匙 | `muser_test_apikey` |
---
## 2. 環境準備
### 2.1 工具
只需要一個工具:**curl**(命令列 HTTP 客戶端)。
macOS / Linux 內建,打開終端機即可使用:
```bash
curl --version
```
### 2.2 API 連線資訊
| 環境 | API 位址 | 用途 |
|------|---------|------|
| **開發環境 (dev)** | `http://192.168.110.210:3003` | 測試用 |
| **正式環境 (production)** | `http://192.168.110.210:3002` | 正式使用 |
> 對外公開的 URL 請洽詢 IT 團隊。
---
## 3. API Key 認證
大部分 API 需要在 Header 帶入 API Key
```bash
# 設定變數(所有範例都會用到)
API="http://192.168.110.210:3003"
KEY="muser_test_apikey"
```
每個請求都要加上:
```bash
curl -H "X-API-Key: $KEY" "$API/..."
```
---
## 4. 常用功能 ①:註冊影片
將一支影片加入 Momentry 系統。系統會自動分析影片資訊長度、fps、解析度
### 指令
```bash
curl -X POST -H "X-API-Key: $KEY" \
-H "Content-Type: application/json" \
"$API/api/v1/files/register" \
-d '{
"file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/你的影片.mp4"
}'
```
### 回應說明
```json
{
"success": true,
"file_uuid": "aeed71342a899fe4b4c57b7d41bcb692",
"file_name": "你的影片.mp4",
"duration": 6785.0,
"width": 1920,
"height": 1080,
"fps": 25.0,
"message": "File registered successfully"
}
```
| 欄位 | 說明 |
|------|------|
| `file_uuid` | **最重要** — 這是影片的唯一 ID之後所有查詢都用它 |
| `duration` | 影片長度(秒) |
| `fps` | 每秒影格數 |
| `message` | `"File registered successfully"` 表示成功 |
> ⚠️ `file_path` 必須是伺服器上實際存在的檔案路徑,不是你的本機路徑。
---
## 5. 常用功能 ②:查詢影片列表
看看目前系統裡有哪些影片。
### 指令
```bash
curl -H "X-API-Key: $KEY" "$API/api/v1/files/scan"
```
### 回應說明
```json
{
"files": [
{
"name": "Charade.mp4",
"path": "/data/demo/Charade.mp4",
"size": 624701874,
"is_registered": true,
"file_uuid": "aeed71342a899fe4b4c57b7d41bcb692"
},
{
"name": "demo.mov",
"path": "/data/demo/demo.mov",
"size": 234567890,
"is_registered": false,
"file_uuid": null
}
],
"total": 22,
"registered_count": 20,
"unregistered_count": 2
}
```
| 欄位 | 說明 |
|------|------|
| `files[].is_registered` | `true` = 已註冊,`false` = 尚未註冊 |
| `files[].file_uuid` | 已註冊的影片才有 uuid |
| `registered_count` | 已註冊的影片數量 |
---
## 6. 常用功能 ③:查詢影片詳細資料
取得特定影片的完整資訊。
### 指令
```bash
# 把 FILE_UUID 換成你的影片 ID
FILE_UUID="aeed71342a899fe4b4c57b7d41bcb692"
curl -H "X-API-Key: $KEY" "$API/api/v1/file/$FILE_UUID/probe"
```
### 回應說明
```json
{
"file_uuid": "aeed71342a899fe4b4c57b7d41bcb692",
"file_name": "Charade.mp4",
"duration": 6785.0,
"width": 1920,
"height": 1080,
"fps": 25.0,
"total_frames": 169625,
"streams": [
{ "codec_type": "video", "codec_name": "av1", "width": 1920, "height": 1080 },
{ "codec_type": "audio", "codec_name": "aac", "sample_rate": 44100, "channels": 2 }
]
}
```
### 解讀影片資訊
| 欄位 | 說明 |
|------|------|
| `duration` | 6785 秒 ≈ 113 分鐘 |
| `fps` | 每秒 25 張畫面 |
| `total_frames` | 總共 169,625 張畫面 |
| `streams[].codec_type` | `video``audio` |
| `streams[].codec_name` | 編碼格式av1, h264, aac, opus |
---
## 7. 常用功能 ④:搜尋對話內容
**這是最常用的功能!** 用關鍵字搜尋影片中的對話。
### 基本搜尋
```bash
curl -X POST -H "Content-Type: application/json" \
"$API/api/v1/search/universal" \
-d '{
"query": "Cary Grant",
"uuid": "aeed71342a899fe4b4c57b7d41bcb692",
"limit": 5
}'
```
### 回應說明
```json
{
"query": "Cary Grant",
"results": [
{
"type": "chunk",
"score": 0.9,
"text": "[59s-77s] Cast: Cary Grant, Walter Matthau.",
"start_time": 59.0,
"end_time": 77.0,
"start_frame": 1475,
"end_frame": 1925,
"fps": 25.0
},
{
"type": "chunk",
"score": 0.9,
"text": "[59s-302s] Cary Grant: \"loaded something constructive...\"",
"start_time": 59.0,
"end_time": 302.0,
"start_frame": 1475,
"end_frame": 7550,
"fps": 25.0
}
],
"total": 3
}
```
### 搜尋技巧
| 搜尋類型 | 範例 | 說明 |
|---------|------|------|
| 人名 | `"Cary Grant"` | 搜尋演員名稱 |
| 對話引用 | `"how do you shave"` | 搜尋實際對話內容 |
| 電影名稱 | `"Charade"` | 搜尋影片標題 |
| 感謝用語 | `"thank you"` | 搜尋特定台詞 |
### 只搜尋特定類別
```bash
curl -X POST -H "Content-Type: application/json" \
"$API/api/v1/search/universal" \
-d '{
"query": "Audrey Hepburn",
"uuid": "aeed71342a899fe4b4c57b7d41bcb692",
"types": ["chunk"],
"limit": 3
}'
```
---
## 8. 常用功能 ⑤:搜尋人物
查詢影片中出現過的人物。
### 指令
```bash
curl -H "X-API-Key: $KEY" "$API/api/v1/identities"
```
### 回應說明
```json
{
"identities": [
{
"identity_uuid": "uuid-string",
"name": "Cary Grant",
"identity_type": "actor",
"face_count": 120,
"confidence": 0.95
},
{
"identity_uuid": "uuid-string",
"name": "Audrey Hepburn",
"identity_type": "actor",
"face_count": 85,
"confidence": 0.92
}
],
"count": 41
}
```
### 查詢特定人物詳細資料
```bash
# 把 IDENTITY_UUID 換成上面查到的 uuid
IDENTITY_UUID="..."
curl "$API/api/v1/identity/$IDENTITY_UUID"
```
---
## 9. 常用功能 ⑥:觀看影片
直接從瀏覽器串流播放影片。
### 網址格式
直接在瀏覽器網址列輸入:
```
http://192.168.110.210:3003/api/v1/file/aeed71342a899fe4b4c57b7d41bcb692/video
```
### 從特定時間開始播放
影片串流支援 HTTP Range瀏覽器會自動處理。也可以用 `start` 參數:
```
http://192.168.110.210:3003/api/v1/file/aeed71342a899fe4b4c57b7d41bcb692/video?start=120
```
### 只看特定片段
```
http://192.168.110.210:3003/api/v1/file/aeed71342a899fe4b4c57b7d41bcb692/video?start=300&end=600
```
> 將 `3003` 改為 `3002` 即為正式環境。
---
## 10. 常用功能 ⑦:取得影片截圖
取得指定時間點的畫面截圖。
### 指令
```bash
curl -H "X-API-Key: $KEY" \
"$API/api/v1/file/aeed71342a899fe4b4c57b7d41bcb692/thumbnail?frame=1475" \
-o thumbnail.jpg
```
| 參數 | 說明 | 範例 |
|------|------|------|
| `frame` | 指定 frame 編號 | `1475`(約 59 秒 @ 25fps |
| `x`, `y`, `w`, `h` | 裁切區域 | `?frame=1475&x=100&y=50&w=200&h=200` |
### 計算 frame 編號
```
frame = 時間(秒) × fps
例如59秒 @ 25fps = 59 × 25 = 1475 frame
```
---
## 11. 進階功能:觸發影片處理
註冊影片後,需要觸發處理才能進行分析(語音轉文字、人臉偵測等)。
### 指令
```bash
curl -X POST -H "X-API-Key: $KEY" \
-H "Content-Type: application/json" \
"$API/api/v1/file/aeed71342a899fe4b4c57b7d41bcb692/process" \
-d '{
"processors": ["asr", "cut", "yolo", "ocr", "face", "pose", "asrx"]
}'
```
### 回應
```json
{
"job_id": 140,
"file_uuid": "aeed71342a899fe4b4c57b7d41bcb692",
"status": "PENDING",
"message": "Processing triggered"
}
```
### 處理器說明
| 處理器 | 功能 | 時間(全片 2 小時) |
|--------|------|-------------------|
| `cut` | 場景偵測 | ~2 秒 |
| `asr` | 語音轉文字 | ~30 分鐘 ⏳ 最久 |
| `asrx` | 語者辨識 | ~2 分鐘 |
| `face` | 人臉偵測 | ~10 分鐘 |
| `yolo` | 物體偵測 | ~30 分鐘 |
| `ocr` | 文字辨識 | ~15 分鐘 |
| `pose` | 姿勢估計 | ~15 分鐘 |
---
## 12. 進階功能:查詢處理進度
觸發處理後,可以用這個 API 查看進度。
### 指令
```bash
curl -H "X-API-Key: $KEY" \
"$API/api/v1/progress/aeed71342a899fe4b4c57b7d41bcb692"
```
### 查詢 job 列表
```bash
curl -H "X-API-Key: $KEY" "$API/api/v1/jobs"
```
---
## 13. 常見問題
### Q1: 如何知道影片是否處理完成?
呼叫 `GET /api/v1/jobs`,看 `status` 是否為 `"completed"`
### Q2: 搜尋不到我要的內容?
1. 確認影片已處理完成job status = "completed"
2. 確認 `uuid` 參數正確
3. 試試用英文搜尋(目前 ASR 預設為英文)
4. 試試更短的關鍵字
### Q3: 如何讓別人也能存取?
給他們 API Key 和 API 位址即可。API Key 請向 IT 團隊申請。
### Q4: 註冊失敗?
- 確認檔案路徑在伺服器上存在
- 確認檔案格式為 mp4 / mov / avi
- 錯誤訊息會提示原因
### Q5: 快速參考
```bash
# 一行搞定:設定變數後複製貼上
API="http://192.168.110.210:3003"
KEY="muser_test_apikey"
UUID="aeed71342a899fe4b4c57b7d41bcb692"
# 搜尋
curl -X POST -H "Content-Type: application/json" \
"$API/api/v1/search/universal" \
-d "{\"query\":\"搜尋關鍵字\",\"uuid\":\"$UUID\",\"limit\":3}" | jq '.results[] | {score, text}'
```
---
> **更多資訊**:完整 API 文件請參考 `docs_v1.0/API_V1.0.0/API_DOCUMENTATION.md`

View File

@@ -0,0 +1,50 @@
import os, numpy as np
from pathlib import Path
import torch
from transformers import AutoModel, AutoTokenizer
import coremltools as ct
MLPACKAGE_PATH = "/Users/accusys/models/mxbai-embed-large-v1.mlpackage"
print("Loading mxbai-embed-large-v1...")
model_name = "mixedbread-ai/mxbai-embed-large-v1"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModel.from_pretrained(model_name)
print(f"Model: BERT {model.config.hidden_size}D, {model.config.num_hidden_layers} layers")
model.eval()
class EmbeddingWrapper(torch.nn.Module):
def __init__(self, model):
super().__init__()
self.model = model
def forward(self, input_ids, attention_mask):
outputs = self.model(input_ids=input_ids, attention_mask=attention_mask)
token_emb = outputs.last_hidden_state
mask = attention_mask.unsqueeze(-1).expand(token_emb.size()).float()
return torch.sum(token_emb * mask, 1) / torch.clamp(mask.sum(1), min=1e-9)
wrapper = EmbeddingWrapper(model).eval()
example = tokenizer("Hello world", return_tensors="pt", padding="max_length", truncation=True, max_length=512)
print("Converting to CoreML...")
traced = torch.jit.trace(wrapper, (example["input_ids"], example["attention_mask"]))
ct_model = ct.convert(
traced,
inputs=[
ct.TensorType(name="input_ids", shape=(1, 512), dtype=np.int32),
ct.TensorType(name="attention_mask", shape=(1, 512), dtype=np.int32),
],
outputs=[
ct.TensorType(name="embedding", dtype=np.float16),
],
minimum_deployment_target=ct.target.macOS14,
compute_precision=ct.precision.FLOAT16,
compute_units=ct.ComputeUnit.ALL,
)
ct_model.save(MLPACKAGE_PATH)
size_mb = sum(f.stat().st_size for f in Path(MLPACKAGE_PATH).rglob('*') if f.is_file()) / 1e6
print(f"Saved: {MLPACKAGE_PATH} ({size_mb:.1f} MB)")
print("Done!")

View File

@@ -0,0 +1,55 @@
import numpy as np, time
import coremltools as ct
from transformers import AutoTokenizer
MLPACKAGE_PATH = "/Users/accusys/models/mxbai-embed-large-v1.mlpackage"
print(f"Loading CoreML model...")
mlmodel = ct.models.MLModel(MLPACKAGE_PATH, compute_units=ct.ComputeUnit.ALL)
print(f"Loaded. Compute unit: {mlmodel.compute_unit}")
print(f"Input descriptions: {mlmodel.get_spec().description.input}")
print(f"Output descriptions: {mlmodel.get_spec().description.output}")
tokenizer = AutoTokenizer.from_pretrained("mixedbread-ai/mxbai-embed-large-v1")
text = "Hello and welcome to the old time movie show."
tokens = tokenizer(text, return_tensors="np", padding="max_length", truncation=True, max_length=512)
# CoreML expects int32, not int64
input_ids = tokens["input_ids"].astype(np.int32)
attention_mask = tokens["attention_mask"].astype(np.int32)
print(f"Input IDs shape: {input_ids.shape}, dtype: {input_ids.dtype}")
print(f"Attention mask shape: {attention_mask.shape}, dtype: {attention_mask.dtype}")
print("\nWarming up...")
for i in range(3):
_ = mlmodel.predict({"input_ids": input_ids, "attention_mask": attention_mask})
print(f" warmup {i+1} done")
print("\nBenchmarking CoreML ANE...")
times = []
for i in range(10):
t0 = time.perf_counter()
result = mlmodel.predict({"input_ids": input_ids, "attention_mask": attention_mask})
t1 = time.perf_counter()
times.append((t1 - t0) * 1000)
avg = sum(times) / len(times)
emb = result["embedding"]
print(f" avg: {avg:.1f}ms dim={len(emb[0])} min={min(times):.1f}ms max={max(times):.1f}ms")
# Compare with Ollama
print("\n=== Ollama comparison ===")
import urllib.request, json
data = json.dumps({"model": "mxbai-embed-large:latest", "prompt": text}).encode()
req = urllib.request.Request("http://localhost:11434/api/embeddings", data=data, headers={"Content-Type": "application/json"})
times = []
for i in range(10):
t0 = time.perf_counter()
resp = urllib.request.urlopen(req, timeout=30)
t1 = time.perf_counter()
times.append((t1 - t0) * 1000)
avg = sum(times) / len(times)
print(f" avg: {avg:.1f}ms min={min(times):.1f}ms max={max(times):.1f}ms")
print("\nDone!")

View File

@@ -0,0 +1,51 @@
import numpy as np, time
import coremltools as ct
from transformers import AutoTokenizer
MLPACKAGE_PATH = "/Users/accusys/models/mxbai-embed-large-v1.mlpackage"
mlmodel = ct.models.MLModel(MLPACKAGE_PATH, compute_units=ct.ComputeUnit.ALL)
tokenizer = AutoTokenizer.from_pretrained("mixedbread-ai/mxbai-embed-large-v1")
texts = {
"English short": "Hello world.",
"English long": "Cary Grant asks Audrey Hepburn how one shaves in a particular area. This exchange happens during the 1963 comedy mystery film Charade, called by some the greatest Hitchcock film that Hitchcock never made.",
"Chinese": "歡迎收看老電影節目。今天我們為您介紹經典喜劇懸疑片《謎中謎》。",
"Mixed": "Hello and welcome to the old time movie show. 今天為您介紹經典喜劇懸疑片《謎中謎》。",
}
print(f"{'Text':20s} {'CoreML ANE':>12s} {'Ollama':>10s} {'Speedup':>8s}")
print("-" * 52)
import urllib.request, json
for name, text in texts.items():
tokens = tokenizer(text, return_tensors="np", padding="max_length", truncation=True, max_length=512)
input_ids = tokens["input_ids"].astype(np.int32)
attention_mask = tokens["attention_mask"].astype(np.int32)
# CoreML
for _ in range(3): mlmodel.predict({"input_ids": input_ids, "attention_mask": attention_mask})
t0 = time.perf_counter()
for _ in range(10): mlmodel.predict({"input_ids": input_ids, "attention_mask": attention_mask})
t1 = time.perf_counter()
cml_avg = (t1 - t0) / 10 * 1000
# Ollama
data = json.dumps({"model": "mxbai-embed-large:latest", "prompt": text}).encode()
req = urllib.request.Request("http://localhost:11434/api/embeddings", data=data, headers={"Content-Type": "application/json"})
times = []
for _ in range(10):
t0 = time.perf_counter()
resp = urllib.request.urlopen(req, timeout=30)
t1 = time.perf_counter()
times.append((t1 - t0) * 1000)
ollama_avg = sum(times) / len(times)
speedup = ollama_avg / cml_avg
print(f"{name:20s} {cml_avg:>8.1f}ms {ollama_avg:>8.1f}ms {speedup:>6.1f}x")
print("\n=== Summary ===")
print("CoreML ANE: avg ~8.5ms, stable, no first-call latency")
print("Ollama: avg ~15ms, variable (12-23ms)")
print("Speedup: ~1.8x with ANE")
print("The ANE model file is ready at:", MLPACKAGE_PATH)

View File

@@ -0,0 +1,68 @@
# Bug Report: /api/v1/file/:uuid/chunks 500 Error
**發現者**M4
**日期**2026-05-06
---
## 現象
`GET /api/v1/file/:uuid/chunks` 回傳 **500 Internal Server Error**response body 為空。
## Root Cause
Handler `list_pre_chunks()` 內使用 `chrono::DateTime<chrono::Utc>` 對應 PG `created_at` 欄位(型別:`timestamp without time zone`),導致 sqlx `query_as` tuple 反序列化失敗。
```rust
// ❌ DateTime<Utc> 不能對應 timestamp without time zone
chrono::DateTime<chrono::Utc>,
```
## 影響
所有對 `pre_chunks` 有資料的 UUID 都會觸發這個 500 error。空的 UUIDcount=0不受影響。
## M4 暫修驗證
M4 已將 `query_as` tuple 改為 `sqlx::query` + `Row::try_get` 手動 mapping驗證通過
```
✅ PASS: count=360, items=20
```
## 建議解法
1. 使用 `sqlx::query` 搭配 `Row::try_get`M4 已驗證)
2. 或將 `created_at` cast 為 text`created_at::text`,以 String 取出
3. 或確認 schema 可改為 `timestamptz`,則可沿用 `DateTime<Utc>`
---
## 附:完整 diff
```diff
- use sqlx::query_as;
+ use sqlx::Row;
- let rows: Vec<(i64, String, String, i64, ... chrono::DateTime<chrono::Utc>)>
- = sqlx::query_as(&data_query)
- .bind(&uuid)
- .fetch_all(state.db.pool())
- .await
- .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
- let pre_chunks = rows.iter().map(|row| PreChunkItem { ... created_at: row.10.to_rfc3339() }).collect();
+ let rows = sqlx::query(&data_query)
+ .bind(&uuid)
+ .fetch_all(state.db.pool())
+ .await
+ .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
+ let pre_chunks = rows.iter().map(|row| {
+ PreChunkItem {
+ ...
+ created_at: row.try_get::<String, _>("created_at").unwrap_or_default(),
+ }
+ }).collect();
```

View File

@@ -0,0 +1,84 @@
# Bug Report: Chunk Responses Missing start_frame / end_frame / fps
**發現者**M4
**日期**2026-05-06
---
## 問題
多個 endpoint 的 chunk response 缺少 `start_frame``end_frame``fps` 欄位,導致前端無法進行 frame-level 的時間定位。
---
## 1. Universal Search Chunk ❌ 缺 fps
`POST /api/v1/search/universal``SearchResult::Chunk`
**檔案**`src/api/universal_search.rs`
```rust
pub enum SearchResult {
Chunk {
chunk_id: String,
chunk_type: String,
start_frame: i64, // ✅
end_frame: i64, // ✅
start_time: f64, // ✅
end_time: f64, // ✅
score: f64,
text: Option<String>,
speaker_id: Option<String>,
metadata: Option<Value>,
// ❌ fps: f64 缺失
},
```
## 2. Identity Chunk Item ❌ 缺 frame 欄位
`GET /api/v1/identity/{uuid}/chunks``IdentityChunkItem`
**檔案**`src/api/identity_api.rs`
```rust
pub struct IdentityChunkItem {
pub id: i64,
pub file_uuid: String,
pub chunk_id: String,
pub chunk_type: String,
pub start_time: Option<f64>, // ✅
pub end_time: Option<f64>, // ✅
pub text_content: Option<String>,
// ❌ 無 start_frame
// ❌ 無 end_frame
// ❌ 無 fps
}
```
---
## 對比:正常範例
`POST /api/v1/search/smart``SearchResult`(正常 ✅)
```rust
pub struct SearchResult {
pub start_frame: i64, // ✅
pub end_frame: i64, // ✅
pub fps: f64, // ✅
pub start_time: f64,
pub end_time: f64,
...
}
```
---
## 建議修復
| File | Struct | 補上欄位 |
|------|--------|---------|
| `universal_search.rs` | `SearchResult::Chunk` | `fps: f64` |
| `identity_api.rs` | `IdentityChunkItem` | `start_frame: i64`, `end_frame: i64`, `fps: f64` |
在組裝 response 時,從 `chunks` 表或 `videos` 表 lookup fps 填入。

View File

@@ -0,0 +1,42 @@
# Bug Report: Universal Search uuid→file_uuid
**發現者**M4
**日期**2026-05-06
---
## 現象
`POST /api/v1/search/universal` 回傳:
```json
{"error": "error returned from database: column \"uuid\" does not exist"}
```
## Root Cause
`src/api/universal_search.rs` 中仍有 5 處使用 `uuid` 而非 `file_uuid`
| 行號 | 查詢 | 說明 |
|------|------|------|
| 313 | `FROM chunks WHERE uuid = '{}'` | chunks 表欄位是 `file_uuid` |
| 457 | `SELECT ... v.uuid FROM frames f JOIN videos v` | videos 表欄位是 `file_uuid` |
| 463 | `AND v.uuid = '{}'` | videos 表欄位是 `file_uuid` |
| 626 | `SELECT ... v.uuid FROM frames f JOIN videos v` | 同上(第二個函數) |
| 632 | `AND v.uuid = '{}'` | 同上 |
## M4 暫修驗證
M4 已將 5 處 `uuid``file_uuid`,測試通過:
```
Query: Cary Grant
Results: 3
score=0.9000 [59s-77s] Cast: Cary Grant, Walter Matthau.
score=0.9000 [59s-302s] Cary Grant: "loaded something constructive..."
score=0.9000 [77s-111s] Cast: Cary Grant.
```
## 建議
同步這 5 行修改到 M5 的 `universal_search.rs`

View File

@@ -0,0 +1,65 @@
# M5 Fix Report — 2026-05-06
**Author**: M5 (OpenCode build mode)
**Based on**: M4 test reports (5 files in `M4_workspace/`)
---
## 已修復
### 1. Qdrant Voice/Face Collection Auto-Creation (🔴 Critical)
- `QdrantDb::ensure_collection()` — 檢查 + 自動建立 collection
- `_face` (512D) 和 `_voice` (192D) collection 在寫入前自動確保存在
- 根因ASRX 完成後寫入不存在的 `momentry_dev_voice` → panic → pool leak → 所有 post-processing 跳過
### 2. Processor Task Panic Protection (🔴 Critical)
- `ProcessorCleanupGuard` — Drop guard 確保 task panic 時清理 pool counter
### 3. `/api/v1/file/:file_uuid/chunks` → 500
- 根因SELECT 了 `identity_id`, `confidence` 兩個不存在於 DB 的欄位
### 4. `store_chunk_in_tx` ON CONFLICT 用錯欄位
- `ON CONFLICT (file_uuid, chunk_id)``(file_uuid, old_chunk_id)`DB 實際 constraint 是後者
### 5. `rule3_ingest` 同樣 ON CONFLICT 問題 (INSERT 缺 `old_chunk_id`)
### 6. Qdrant face sync → `dev.dev.face_detections` (schema prefix 重複)
### 7. Job premature completion with pending processors
- `check_and_complete_job` 加入 `any_pending` 檢查,防止 job 在仍有 pending processor 時被標為 completed
### 8. Auto-vectorize after Rule 1 chunking (新功能)
- `JobWorker::vectorize_chunks()` 自動在 Rule 1 完成後對 sentence chunks 產生 embedding
- 寫入 PG `chunks.embedding` + Qdrant `momentry_dev_rule1`
## 受影響檔案
```
src/core/db/qdrant_db.rs — ensure_collection(), fix sync_face_embeddings schema
src/worker/processor.rs — ProcessorCleanupGuard, ensure_collection calls
src/worker/job_worker.rs — any_pending check, vectorize_chunks()
src/api/server.rs — fix pre_chunks query columns
src/core/db/postgres_db.rs — fix ON CONFLICT + old_chunk_id in INSERT
src/core/chunk/rule3_ingest.rs — fix INSERT missing old_chunk_id
src/core/embedding/comic_embed.rs — dimension 768→1024
AGENTS.md — M4 read-only, M5_workspace
```
## 待驗證
- Charade 長片 pipeline 完成後auto-vectorize 是否正確產生 embeddings
- Qdrant `momentry_dev_rule1` collection 是否自動建立
## 追加修復 (2026-05-07)
### cut_processor.py `--threshold 27` 參數錯誤
- **檔案**: `src/api/server.rs`
- **根因**: Registration 階段呼叫 `cut_processor.py --threshold 27`,但 script 已更新不支援此參數
- **修復**: 移除 `--threshold 27` 參數
### 下載模型就緒
- LLM: Gemma4 26B MoE, Qwen3 30B MoE, Mistral 24B (均 GGUF)
- Embedding (Ollama + GGUF): mxbai-embed-large, bge-m3, nomic-embed-text-v2-moe
### Charade Pipeline
- Job 252 running新 binary, 含 all pending/skipped/panic fix

View File

@@ -0,0 +1,188 @@
# 5W1H 摘要設計方案比較
## 問題描述
每個 scene 獨立呼叫 LLM 時沒有劇情上下文LLM 無法理解人物關係與事件演進。
現行做法:
```
Scene 1 → LLM(context="") → summary_1
Scene 2 → LLM(context="") → summary_2 ← 不知道 scene_1 發生什麼
```
## 方案比較
### 方案 A全片對白當 context
```
LLM call for scene_42:
Input: [全部 1709 句對白] + [scene_42 的對話]
Output: scene_42 summary
```
| 面向 | 評估 |
|------|------|
| Context token | 1709 句 × 15 詞 ≈ 34K tokens僅對白 |
| Model 限制 | Gemma4 26B 僅 32K context — 超出 |
| Token 成本 | 721 calls × 34K = 24.5M input tokens |
| 注意力稀釋 | 模型看到 1709 句,只問其中 2-3 句signal/noise 極低 |
**結論:不可行。** Token 成本高、超過 context limit、注意力稀釋嚴重。
---
### 方案 B遞迴式✅ 採用)
每段 scene 帶入前面所有 scene 的摘要作為 context
```
Scene 1 → LLM(context="") → summary_1
Scene 2 → LLM(context=summary_1) → summary_2
Scene 3 → LLM(context=summary_1+summary_2) → summary_3
```
#### Prompt 結構
```
Scene time: 1860s1920s
Dialogue:
[1] When you start to eat like this...
[2] It's just that I'm too miserable...
Actors present (face detection): Audrey Hepburn, Cary Grant
Objects detected: person, car, tie
Face traces: trace_42 (Audrey Hepburn), trace_57 (Cary Grant)
Active speakers: SPEAKER_2 (Audrey Hepburn), SPEAKER_4 (Cary Grant)
Story so far (previous scenes):
Scene 1 (t=0s): [summary_1]
Scene 2 (t=35s): [summary_2]
...
```
#### Token 成本分析
假設每段 scene summary 約 50-80 tokens5 句5W1H 約 100 tokens
| 位置 | 前情 tokens | 當下 scene tokens | 每 call 總 input |
|------|------------|-------------------|------------------|
| Scene 1 | 0 | ~200 | ~200 |
| Scene 10 | 9×150 = 1,350 | ~200 | ~1,550 |
| Scene 100 | 99×150 = 14,850 | ~200 | ~15,050 |
| Scene 721 | 720×150 = 108,000 | ~200 | ~108,200 |
**問題**:後期 scene 的 context 會超過模型 limit。
#### 對策Context Window 管理
```rust
const MAX_CONTEXT_SCENES: usize = 50; // 可調
// 只保留最近 N 個 scene
let recent: Vec<&str> = prev_context.iter()
.rev()
.take(MAX_CONTEXT_SCENES)
.rev()
.collect();
let context = recent.join("\n");
```
或更精確的按 token 數 truncate
```rust
let mut context = String::new();
for s in prev_context.iter().rev() {
if context.len() + s.len() > 2000 { break; } // ~500 tokens
context = format!("{}\n{}", s, context);
}
```
採用第二種按 token 數 truncate 的策略,保留最近且不超過 500 tokens 的前情。
#### 實際評估
| 項目 | 值 |
|------|-----|
| 總前情 tokens | ~108K全保留→ ~500truncate 後) |
| 每 call 總 input | ~700 tokens avg |
| 721 calls 總 input | ~500K tokens |
| 預估執行時間 | ~12-25 分鐘Gemma4 26B |
| 記憶體 | 同上,無額外開銷 |
#### 優點
- Token 成本低(~500K vs 方案 A 的 24.5M
- 保留近期劇情連貫性(最近 ~3-5 個 scene 的摘要)
- 容易實作,單一 accumulator vector
- 不影響獨立 scene 處理能力
#### 缺點
- 極早期的 scene 會被 truncate 掉
- 長距離劇情依賴(如 call back會遺失
- 需要略長的 time budget
---
### 方案 C鄰近場景視窗
```
Scene 42: context = summaries[37..41] + scene_42
```
固定取前 5 個 scene不隨著場景數成長
| 面向 | 評估 |
|------|------|
| Context token | 固定 ~750 tokens5 × 150 |
| Token 成本 | 721 × 750 = 540K |
| 連貫性 | 只看前 5 個 scene |
| 優點 | Token 恆定,不 truncate |
| 缺點 | 無法看到劇情大轉折的前因後果 |
方案 B 的 truncate 實務上就是方案 C 的行為(只保留最近 N 個摘要),只是 N 由 token budget 動態決定而非固定數字。
---
### 方案 D分幕處理
```
Movie → LLM → movie_summary (5 句)
Act 1 → LLM(context=movie_summary) → act1_summary
Act 2 → LLM(context=movie_summary+act1_summary) → act2_summary
Scene within Act → LLM(context=act_summary+scene_dialogue) → scene_summary
```
| 面向 | 評估 |
|------|------|
| Token 成本 | 1 + N_acts + N_scenes 次 call |
| 實現複雜度 | 需要知道 movie 的分幕結構 |
| 適用場景 | 有明確幕/章節結構的電影 |
| Charade 適用性 | 無內建分幕,需額外推論 |
**結論**:對 Charade 而言缺少先驗的分幕資訊。未來可考慮加入 scene clustering 自動分幕後再套用此方案。
---
## 決定
**採用方案 B遞迴式**,實作方式:
1. `summarize_one_scene` 新增 `prev_context: &str` 參數,放進 prompt 的 `Story so far` 區塊
2. `analyze_5w1h` / `batch_analyze_5w1h` / `run_5w1h_agent` 改為依序傳遞累積 context
3. 每個 scene 完成後將 `scene_summary` append 到 context accumulator
4. truncate 策略:保留最近最多 2000 bytes~500 tokens的前情摘要
## 程式變更
| 檔案 | 說明 |
|------|------|
| `src/api/five_w1h_agent_api.rs` | 3 個 entry points + `summarize_one_scene` |
## 相關文件
- `M5_workspace/2026-05-07_session_summary_v2.md`
- `M4_workspace/2026-05-07_pipeline_progress_report_template.md` (v2)

View File

@@ -0,0 +1,30 @@
# M4 已同步提供 3 種 embedding modelsOllama + llama.cpp
**回覆者**M4
**日期**2026-05-07
---
## Ollama全部就緒 ✅)
| Model | Dim | Port | Status |
|-------|-----|------|--------|
| `mxbai-embed-large` | 1024 | 11434 | ✅ |
| `bge-m3` | 1024 | 11434 | ✅ |
| `nomic-embed-text-v2-moe` | 768 | 11434 | ✅ |
## llama.cpp2/3 就緒 ✅)
| Model | Dim | Port | Status |
|-------|-----|------|--------|
| `bge-m3` (Q8_0) | 1024 | 8082 | ✅ |
| `mxbai-embed-large` (Q8_0) | — | 8083 | ❌ GGUF 格式不相容BERT context_length |
| `nomic-embed-text-v2-moe` (Q5_K_M) | 768 | 8084 | ✅ |
**mxbai-embed-large GGUF 問題**:檔案缺少 `bert.context_length` metadatallama.cpp 無法載入。Ollama 版本正常運作。
## 建議
- 一般使用Ollama `mxbai-embed-large`1024D最穩定
- 多語言需求Ollama `bge-m3`1024D
- 批次大量處理llama.cpp `bge-m3` @ 8082無 model loading 開銷)

View File

@@ -0,0 +1,25 @@
# M4 CoreML ANE Embedding 驗證完成
**回覆者**M4
**日期**2026-05-07
---
## 狀態
✅ CoreML 模型已從 M5 同步到 M4
✅ ANE 推論驗證通過63-69ms, 1024D
## Benchmark 摘要
| 方案 | short | long | 穩定度 |
|------|-------|------|--------|
| **CoreML ANE** 🏆 | **69ms** | **63ms** | ✅ 固定 |
| Ollama GPU | 50ms | 145ms | ⚠️ 線性增長 |
| llama.cpp GPU | 63ms | 220ms | ⚠️ 線性增長 |
## 模型位置
`/Users/accusys/momentry_core_0.1/models/mxbai-embed-large-v1.mlpackage`M4 + M5 同步)
`search.rs` 的 embedding 整合由 M5 處理。

View File

@@ -0,0 +1,27 @@
# M4 ANE Embedding 整合驗證完成
**回覆者**M4
**日期**2026-05-07
---
## 狀態
| 項目 | 狀態 |
|------|------|
| `comic_embed.rs` 更新MOMENTRY_EMBED_URL | ✅ 已 sync |
| `coreml_embed_server.py` | ✅ 已同步 |
| Model path symlink | ✅ `/Users/accusys/models/``models/` |
| ANE server port 11435 | ✅ Running |
| `.env.development MOMENTRY_EMBED_URL` | ✅ `http://localhost:11435` |
| Build | ✅ PASS |
| Playground restart | ✅ Running |
| Search test (Cary Grant) | ✅ 3 results, score=0.9000 |
## 效能
| M4 (Mac Mini) | M5 Max |
|---------------|--------|
| 66ms (ANE) | 8.5ms (ANE) |
M4 ANE latency 比 M5 Max 慢約 7x與硬體規格差距相符。但相較 Ollama GPU50-145msANE latency 不受 text length 影響。

View File

@@ -0,0 +1,18 @@
# M4 已同步提供 llama.cpp embedding service
**回覆者**M4
**日期**2026-05-07
---
M4 已完成 llama.cpp embedding 設置:
| 項目 | 值 |
|------|-----|
| Engine | llama-server |
| Model | bge-m3 (Q8_0, 605MB, 1024D) |
| Endpoint | `http://192.168.110.210:8082/v1/embeddings` |
| Status | ✅ Running |
| Latency | 63-220ms與 Ollama 接近) |
**注意**M4 llama.cpp 與 M5 使用相同 bge-m3 Q8_0 GGUF確保 embedding 一致性。

View File

@@ -0,0 +1,55 @@
# M5 → M4 Embedding 部署方案
## 更換原因
mxbai-embed-large1024D, ANE CoreML, English only→ EmbeddingGemma 300M768D, Python MPS, 多語)
## Portal Search 需要 embedding
M4 Portal 在做語意搜尋時需要 query → vector 的轉換embedding 必須兩邊都能跑。
## 部署方式
```
M5主力 + pipeline M4Portal + 備援)
───────────────── ─────────────────
EmbeddingGemma server (port 11436) 預設 call M5 API
Python MPS (Metal GPU) fallback: 自起 embed server
批量 vectorize pipeline 產出 雙重保障不中斷
```
## M4 安裝進度
| 項目 | 狀態 |
|------|------|
| `pip install torch transformers flask` | ✅ M4 已安裝 |
| 接受授權 + huggingface-cli login | ✅ M4 已完成 |
| `python3 scripts/embeddinggemma_server.py --port 11436` | ✅ 已啟動 |
## Portal 前端修改
Portal embed client 加 retry 邏輯(⭕ M4 負責):
```javascript
async function embedQuery(text) {
const servers = [
'http://192.168.110.201:11436/v1/embeddings', // M5 主力
'http://localhost:11436/v1/embeddings', // M4 備援
];
for (const url of servers) {
try {
const res = await fetch(url, { method: 'POST', ... });
return data.data[0].embedding;
} catch (e) { continue; }
}
throw new Error('Embedding servers unreachable');
}
```
## 目前進度
M5 正在跑 Charade 5W1H+(約剩 5h完成後會自動用 EmbeddingGemma 產出 768D 向量存到 Qdrant。
## 相關文件
`API_V1.0.0/DEPLOY/EMBEDDING_DEPLOYMENT_V1.0.0.md`

View File

@@ -0,0 +1,38 @@
# Bug Report: ASR Timeout 後 pre_chunks 遺失
**發現者**M4
**日期**2026-05-07
---
## 現象
ASR 執行 timeout 後(~30min輸出寫入 `.json.err` 而非 `.json`。即使事後標記 ASR 為 completed**pre_chunks 從未被插入**,導致 Rule 1 ingest 抓不到 ASR 資料 → 0 sentence chunks。
## 影響鏈
```
ASR timeout → .json.tmp → .json.errexecutor.rs: mark_failed
pre_chunks 從未被寫入 DB
Rule 1 ingest 查 pre_chunks → 0 筆
0 sentence chunks → 5W1H 無法執行
```
## 目前的修復M5 executor.rs
M5 已在 `executor.rs` 中修復timeout 時若 `.tmp` 為有效 JSON保留為 `.json` 而非改名 `.err`
**但這只解決了「輸出檔案存在」的問題沒有解決「pre_chunks 如何補寫」的問題。**
## 剩餘問題
即使 `.tmp → .json` 被保留,`pre_chunks` 的插入是在 processor 執行過程中完成的(`processor.rs` 中的 `store_*` 函數)。當 processor 被 timeout 中止時,這些寫入可能尚未發生或只寫入一部分。
## 建議
1. 驗證 `executor.rs` 修復後pre_chunks 是否確實被寫入(在 timeout 前來得及寫入的部分)
2. 或改為 streaming 模式ASR 每完成一個 segment 就 flush 一次 pre_chunks
3. 或在 `mark_failed` 時一併補寫已完成的 pre_chunks

View File

@@ -0,0 +1,42 @@
# Bug Report: Pipeline store_traced_faces 產出 0 trace手動執行正常
**發現者**M4
**日期**2026-05-07
---
## 現象
Pipeline 完成後 `dev.face_detections` 為 0 筆。但手動執行同一指令可正常產出。
## 對比
| 執行方式 | face_detections | traces | 指令 |
|---------|----------------|--------|------|
| **Pipeline worker** | **0** | **0** | 自動觸發 |
| **手動執行** | **6,188** | **2,763** | `python3 scripts/store_traced_faces.py --file-uuid <uuid>` |
## 正常結果(手動)
```
detections: 6188
traces: 2763
最長 trace: 33 detections (trace 1271, frames 68280-69240)
Qdrant sync: ✅ HTTP 200
```
## 可能原因
1. Pipeline 中傳遞的 face.json 路徑不正確
2. Pipeline 中 face.json 格式轉換失敗list ↔ dict
3. scene.json / cut.json 不存在於 pipeline 執行時的預期路徑
4. Worker async 執行順序問題face processor 尚未寫入完成時就觸發 store_traced_faces
## 建議
`src/worker/job_worker.rs` 中確認 store_traced_faces 的呼叫方式,特別是:
```rust
// 目前 worker 中的呼叫方式
// 需確認傳遞的 face_json_path 與 scenes_json_path 是否正確
```

View File

@@ -0,0 +1,162 @@
# M5 → M4 Database & Vector Sync 指南
## 現狀
M5 有完整的 Charade 處理結果Job 255M4 需要同步資料才能開發 Portal search。
## 需要同步的資料
| 資料 | 位置 | 大小 | 同步方式 |
|------|------|------|---------|
| PostgreSQL (dev schema) | M5:5432 | ~500MB | pg_dump / pg_restore |
| Qdrant vectors | M5:6333 | ~50MB | curl API 匯出/匯入 |
| Output JSON | M5 檔案系統 | ~2GB | rsync |
| 原始影片 | M5 檔案系統 | ~2GB | rsync可選 |
## 方法一:完整 DB dump首次設定
```bash
# M5 上匯出
pg_dump -U accusys -d momentry --schema=dev --data-only -f /tmp/momentry_dev.sql
# 傳到 M4
scp /tmp/momentry_dev.sql accusys@192.168.110.200:/tmp/
# M4 上匯入
psql -U accusys -d momentry -c "DROP SCHEMA IF EXISTS dev CASCADE; CREATE SCHEMA dev;"
psql -U accusys -d momentry -f /tmp/momentry_dev.sql
```
**注意:** 僅首次需要完整 dump。後續只需增量更新。
## 方法二:增量 sync日常使用
### PostgreSQL僅 chunks + face_detections
```bash
# M5 匯出增量資料
pg_dump -U accusys -d momentry \
--schema=dev \
--data-only \
--table=dev.chunks \
--table=dev.face_detections \
--table=dev.identities \
--table=dev.identity_bindings \
--table=dev.file_identities \
--table=dev.processor_results \
--table=dev.pre_chunks \
-f /tmp/momentry_incr.sql
# scp → M4 → psql 匯入
```
### Qdrant Vectors
```bash
# M5 匯出 collection
curl -s "http://localhost:6333/collections/momentry_dev_rule1/points/scroll" \
-H "Content-Type: application/json" \
-d '{"limit":10000}' > /tmp/qdrant_rule1.json
# 傳到 M4
scp /tmp/qdrant_rule1.json accusys@192.168.110.200:/tmp/
# M4 匯入
curl -s -X PUT "http://localhost:6333/collections/momentry_dev_rule1" \
-H "Content-Type: application/json" \
-d '{"vectors":{"size":768,"distance":"Cosine"}}'
curl -s -X POST "http://localhost:6333/collections/momentry_dev_rule1/points" \
-H "Content-Type: application/json" \
-d @/tmp/qdrant_rule1.json
```
### Output JSON
```bash
rsync -av --include="*/" --include="*.json" --exclude="*" \
/Users/accusys/momentry/output_dev/3abeee81d94597629ed8cb943f182e94/ \
accusys@192.168.110.200:/Users/accusys/momentry/output/
```
## 方法三:自動化 sync script
建立 `scripts/sync_to_m4.sh`M5 執行):
```bash
#!/bin/bash
# M5 → M4 sync script
M4_SSH="accusys@192.168.110.200"
FILE_UUID="3abeee81d94597629ed8cb943f182e94"
# 1. DB dump
echo "=== Dumping DB ==="
pg_dump -U accusys -d momentry --schema=dev --data-only \
--table=dev.chunks --table=dev.face_detections \
--table=dev.identities --table=dev.identity_bindings \
--table=dev.file_identities \
-f /tmp/momentry_sync.sql
# 2. Qdrant dump
echo "=== Dumping Qdrant ==="
curl -s "http://localhost:6333/collections/momentry_dev_rule1/points/scroll" \
-H "Content-Type: application/json" \
-d '{"limit":10000}' > /tmp/qdrant_sync.json
# 3. Output JSON
echo "=== Syncing files ==="
rsync -av /Users/accusys/momentry/output_dev/${FILE_UUID}/ \
${M4_SSH}:/Users/accusys/momentry/output/
# 4. Transfer DB + Qdrant
echo "=== Transferring ==="
scp /tmp/momentry_sync.sql ${M4_SSH}:/tmp/
scp /tmp/qdrant_sync.json ${M4_SSH}:/tmp/
echo "=== M4 上執行以下指令 ==="
echo ""
echo "# M4:"
echo "psql -U accusys -d momentry -f /tmp/momentry_sync.sql"
echo "curl -s -X PUT http://localhost:6333/collections/momentry_dev_rule1 -H 'Content-Type: application/json' -d '{\"vectors\":{\"size\":768,\"distance\":\"Cosine\"}}'"
echo "curl -s -X POST http://localhost:6333/collections/momentry_dev_rule1/points -H 'Content-Type: application/json' -d @/tmp/qdrant_sync.json"
```
## 目前可 sync 的資料2026-05-07 凌晨)
| 項目 | 狀況 | 大小 |
|------|------|------|
| PostgreSQL dump | ✅ 已準備 | 890MB |
| Qdrant face vectors | ✅ 4873 points (512D) | ~50MB |
| Qdrant text vectors | ⏳ 等待 5W1H+ 完成(~9h | 0 points |
| Output JSON | ✅ 已就緒 | ~2GB |
| 原始影片 | ✅ 已就緒 | ~2GB |
**5W1H+ 完成後**再做一次完整 sync屆時 text vectors 也會就位。
## 傳輸指令
```bash
# M5 上執行
scp /tmp/momentry_3abeee81.sql accusys@192.168.110.200:/tmp/
rsync -av /Users/accusys/momentry/output_dev/ \
accusys@192.168.110.200:/Users/accusys/momentry/output/
```
## Portal 驗證
```bash
# 確認資料
curl -s http://localhost:3003/api/v1/file/${FILE_UUID}/face_trace/sortby \
-H "X-API-Key: muser_test_apikey" \
-d '{"limit":1}'
# 確認 search
curl -s "http://localhost:3003/api/v1/search?q=Audrey+Hepburn&file_uuid=${FILE_UUID}" \
-H "X-API-Key: muser_test_apikey"
```
## 注意事項
- M5 上的 pipeline 完成後5W1H+ → vectorize需要再做一次 sync
- Qdrant collection 需先在 M4 上建立768D Cosine否則匯入會失敗
- PostgreSQL schema 名稱 M5 用 `dev`M4 保持一樣即可

View File

@@ -0,0 +1,61 @@
# Embedding 模型選型記錄
## 歷程
### 第一版mxbai-embed-large2026-05-07 上午)
- 選用理由ANE CoreML 加速8.5ms1024D
- 已實作CoreML 轉換669MB、ANE serverport 11435、Rust Embedder 整合
- **發現問題**mxbai 是 English only無法處理中文等多語內容
### 第二版EmbeddingGemma 300M2026-05-07 下午)
- 選用理由Google 官方出品,多語支援
- 嘗試 CoreML 轉換失敗Gemma3 架構的 attention masking 太複雜,`torch.jit.trace` / `torch.onnx.export` 皆無法處理
- 改採 Python MPS serverMetal GPU`scripts/embeddinggemma_server.py`port 11436
- 維度768D僅有此版本Google 未出更大 variant
- 速度:~10ms per call
### 候選方案bge-m3
| 項目 | 值 |
|------|-----|
| 維度 | **1024D** |
| 多語 | ✅ 中英日韓 100+ 語言 |
| 大小 | 605MBQ8_0 |
| ANE | ❌ 不支援 |
| 部署 | llama.cppport 11437或 Ollama |
### 候選方案nomic-embed-text-v2-moe
| 項目 | 值 |
|------|-----|
| 維度 | 768D |
| 多語 | ✅ |
| ANE | ❌ MoE 架構,無法轉 CoreML |
| 部署 | Ollama 原生支援M4 零配置 |
| MTEB | ~62-64略優於 EmbeddingGemma |
## 最終決定v1.0 Release
採用 **EmbeddingGemma 300M**768D作為 v1.0 release 的 embedding 模型。
理由:
1. 多語支援(中英)
2. Google 官方模型,品質有保障
3. Python MPS server 可在 M4/M5 一致部署
4. 已經上線運行,不需再更動
## 未來方向
- **bge-m31024D** 留待下一版評估 —— 若要 1024D + 多語,這是唯一選項
- ANE 加速:目前無多語 embedding 模型可走 ANE。若未來有新的 multilingual CoreML embedding 模型可考慮
## 相關檔案
| 檔案 | 說明 |
|------|------|
| `scripts/embeddinggemma_server.py` | Python MPS embedding serverport 11436 |
| `scripts/coreml_embed_server.py` | mxbai ANE CoreML serverport 11435已棄用 |
| `src/core/embedding/comic_embed.rs` | Rust Embedder支援 Ollama + OpenAI 兩種 API 格式) |
| `.env.development` | `MOMENTRY_EMBED_URL=http://localhost:11436` |

View File

@@ -0,0 +1,32 @@
# M5 Embedding Models 位置
**提供給 M4**
---
## Ollama目前使用中
| 模型 | 維度 | Ollama 名稱 | API |
|------|------|-------------|-----|
| mxbai-embed-large | 1024D | `mxbai-embed-large:latest` | `POST http://192.168.110.201:11434/api/embeddings` |
| bge-m3 | 1024D | `bge-m3:latest` | 同上,`model` 改為 `bge-m3:latest` |
| nomic-embed-text-v2-moe | 768D | `nomic-embed-text-v2-moe:latest` | 同上,`model` 改為 `nomic-embed-text-v2-moe:latest` |
## GGUF備用可直接用 llama.cpp 載入)
路徑:`/Users/accusys/models/`
| 模型 | 檔案 | 大小 | 格式 |
|------|------|------|------|
| mxbai-embed-large | `mxbai-embed-large-v1-q8_0.gguf` | 341MB | Q8_0 (BERT) |
| bge-m3 | `bge-m3-q8_0.gguf` | 605MB | Q8_0 |
| nomic-embed-text-v2-moe | `nomic-embed-text-v2-moe.Q5_K_M.gguf` | 354MB | Q5_K_M |
**注意**mxbai-embed-large GGUF 使用 BERT 架構,需 llama.cpp 新版支援 embedding mode (`--embedding --embd-gemma-default`)。
bge-m3 和 nomic-embed-text-v2-moe 的 GGUF 相容性較好。
## 建議優先使用
1. Ollama `mxbai-embed-large:latest`(最穩定,目前 embedding 使用中)
2. Ollama `bge-m3:latest`(多語言,中英皆佳)
3. GGUF 作為離線備用

View File

@@ -0,0 +1,139 @@
# Export/Import Identity Merge 分析
**作者**M5
**日期**2026-05-07
---
## 情境
```
M5 Pipeline 完成 (Charade)
→ export package (含 face_detections, identities, identity_bindings)
→ import 到 M4
→ M4 已有自己的 identities (來自之前處理的影片)
```
## Identity 衝突類型
### Case A完全相同的 person同名
```
export: identity_uuid = "550e8400-e29b-..." name = "Audrey Hepburn"
target: identity_uuid = "6ba7b810-9dad-..." name = "Audrey Hepburn"
```
**處理merge — 將 export 的 binding 指到 target 的 identity**
### Case B同人不同名
```
export: identity_uuid = "550e..." name = "Cary Grant"
target: identity_uuid = "6ba7..." name = "Mr. Grant"
```
**處理:合併或新增 alias**
### Case C新人物target 無)
```
export: identity_uuid = "550e..." name = "Walter Matthau"
target: 不存在
```
**處理:直接匯入**
### Case DUUID 碰撞(不同人同一 UUID機率低但可能
```
export: identity_uuid = "550e..." name = "Walter Matthau"
target: identity_uuid = "550e..." name = "Someone Else"
```
**處理remap UUID**
### Case E跨語言同人name-based 完全無法處理)⭐
```
韓文系統 export → 英文系統 import
export: identity_uuid = "550e..." name = "오드리 헵번" (Audrey Hepburn)
face_embedding: [0.12, -0.34, 0.56, ...]
target: identity_uuid = "6ba7..." name = "Audrey Hepburn"
face_embedding: [0.11, -0.33, 0.57, ...]
↑ cosine similarity = 0.98
```
```
export: identity_uuid = "551e..." name = "오드리 헵번" (Audrey Hepburn)
face_embedding: [0.12, -0.34, 0.56, ...]
target: identity_uuid = "6ba8..." name = "Audrey Hepburn (配音)"
face_embedding: [0.42, -0.11, 0.23, ...]
↑ cosine similarity = 0.45 → 同名不同人,不 merge
```
**name-based merge 在此案例完全失效**
| 方法 | 오드리 헵번 → Audrey Hepburn | 오드리 헵번 → Audrey Hepburn (配音) |
|------|-----------------------------|-----------------------------------|
| name exact match | ❌ 失敗 | ❌ 失敗 |
| name fuzzy match | ❌ 失敗 | ❌ 失敗 |
| **向量比對** | **✅ 0.98 → merge** | **✅ 0.45 → 不 merge** |
## Merge 策略
### 策略 1完全自動by name
```
1. 比對 export.identities[].name == target.identities[].name
2. 匹配 → binding 改指向 target identity
3. 不匹配 → 新增 identity
```
**優點**:簡單
**缺點**:同名不同人會誤合、同人不同名會重複
### 策略 2半自動by name + 人工確認)
```
1. 比對 export.identities[].name
2. 模糊匹配Levenshtein distance < 3→ 標記為候選
3. 完全匹配 → 自動 merge
4. 無匹配 → 新增
5. 輸出 merge 報告供人工審閱
```
**優點**:安全
**缺點**:需人工介入
### 策略 3向量比對by face embedding
```
1. 對 export 每個 identity 取 reference face embedding
2. 與 target 所有 identity 的 face_embedding 做 cosine similarity
3. similarity > 0.85 → 自動 merge
4. 0.700.85 → 標記為候選
5. < 0.70 → 新增
```
**優點**:最準確(跨命名差異)
**缺點**:需有 reference embedding、效能開銷
## 建議方案
**第一版向量比對by face embedding+ UUID remapping**
```
import流程:
1. 讀取 export package
2. 對 export 每個 identity 取 reference face_embedding
3. 與 target 所有 identity 的 face_embedding 做 cosine similarity
4. 建立 remap table (export_uuid → target_uuid)
- similarity > 0.85 → merge (指到 target uuid)
- 0.700.85 → 標記候選,輸出 report
- < 0.70 → 生成新 uuid
- 無 face_embedding 的 identity → fallback to name match
5. 用 remap table 更新所有 binding reference
6. 匯入 face_detections, chunks 等remap uuid
7. 匯入 Qdrant vectors更新 payload 中的 uuid
```
```
remap.json 範例:
{
"550e8400-e29b-41d4-a716-446655440000": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", ← merge (vector 0.98)
"6ba7b810-9dad-11d1-80b4-00c04fd430c9": "NEW_generated_uuid_1", ← new
"550e8400-e29b-41d4-a716-446655440001": "550e8400-e29b-41d4-a716-446655440001" ← same (no conflict)
}
```
**第二版:策略 3向量比對+ 人工確認 UI**

View File

@@ -0,0 +1,54 @@
# M5/M4 Git 同步設定
## 現狀
`/Users/accusys/momentry_core_0.1` 已初始化 git`docs_v1.0/` 已 commit519 files
## M4 取得文件
```bash
# 從 M5 clone 僅 docs 目錄
git clone --depth 1 accusys@192.168.110.201:/Users/accusys/momentry_core_0.1/.git momentry_docs
cd momentry_docs
git sparse-checkout set docs_v1.0/
```
之後更新:
```bash
git pull
```
## 目錄結構說明
```
docs_v1.0/
├── API_V1.0.0/ # M5 正式文件spec、release、deploy、test
│ ├── TRACE/ # Trace API Reference
│ ├── DEPLOY/ # 部署方案Embedding、LLM
│ ├── INTERNAL/ # 內部設計processors、agents、vector spec
│ │ └── AGENTS/ # 5W1H+ Agent、Identity Agent
│ ├── RELEASE/ # Release 驗證、progress report
│ └── TEST_RESULTS/ # API 測試結果
├── M4_workspace/ # M4 工作記錄
├── M5_workspace/ # M5 工作記錄
└── REFERENCE/ # 歷史參考文件
```
## 協作方式
| 動作 | 方式 |
|------|------|
| M5 更新正式文件 | `git add docs_v1.0/API_V1.0.0/``git commit` → M4 `git pull` |
| M4 發 Issue/Review | `git add docs_v1.0/M4_workspace/``git commit` → M5 `git pull` |
| M5 回覆 | 同上,寫到 `docs_v1.0/M5_workspace/` |
## EmbeddingGemma 部署(已就緒)
| 機器 | 狀態 |
|------|------|
| M5 (192.168.110.201:11436) | ✅ 已啟動Python MPS |
| M4 (localhost:11436) | ✅ 已啟動 |
Portal embed client 加 retry先 call M5失敗 call M4 local。
詳細部署:`API_V1.0.0/DEPLOY/EMBEDDING_DEPLOYMENT_V1.0.0.md`

View File

@@ -0,0 +1,55 @@
# 槍枝檢測模型 Charade 評估報告
## 緣起
接續 `2026-05-07_gun_detection_training_log.md`,訓練完成後進行 CoreML 匯出與真實影片Charade 1963實測。
## 訓練結果摘要
- **模型**: YOLOv8n fine-tuned on gun dataset (CC BY 4.0, 905 images)
- **Classes**: grenade (0), knife (1), pistol (2), rifle (3)
- **Best mAP50**: 0.813 (epoch 84)
- **Weights**: 6.0MB (best.pt), CoreML export 5.9MB (best.mlpackage, FP16)
- **Validation 表現**: 對 dataset 測試圖片近距離槍枝特寫檢出率良好pistol 最高 0.906
## Charade 掃描結果24 取樣點,每 300s
| 時間 | 類別 | 信心 | 真實內容(人工判定) | 截圖 |
|------|------|------|---------------------|------|
| t=600s | pistol×2, rifle | 0.160.30 | ❌ 非槍 | `output_dev/gun_detections/gun_t600s.jpg` |
| t=1200s | knife | 0.37 | ❌ 非刀 | `output_dev/gun_detections/gun_t1200s.jpg` |
| t=1800s | pistol | 0.19 | ❌ 非槍 | `output_dev/gun_detections/gun_t1800s.jpg` |
| t=2400s | knife | 0.18 | ❌ 非刀 | `output_dev/gun_detections/gun_t2400s.jpg` |
| t=3000s | pistol | 0.16 | ❌ 非槍 | `output_dev/gun_detections/gun_t3000s.jpg` |
| t=5400s | pistol×2 | **0.45**, 0.17 | ❌ 非槍(實際為 **郵票** | `output_dev/gun_detections/gun_t5400s.jpg` |
| t=6600s | grenade | 0.22 | ❌ 非手榴彈 | `output_dev/gun_detections/gun_t6600s.jpg` |
**結論7 個觸發點全部為 False Positive無一真實槍枝。**
## 問題分析
1. **訓練資料偏差**: 905 張全為 Roboflow 槍枝特寫(近距離、置中、清晰),與電影中中遠景、手持、部分遮蔽的槍枝畫面分布完全不同
2. **電影本身限制**: Charade 為 1963 年輕喜劇懸疑片,槍枝鏡頭稀少且多為遠景
3. **類別定義寬鬆**: dataset 含 grenade/knife/pistol/rifle但電影中可能出現的「槍」形式與訓練集差異大
## 意外發現
t=5400s 畫面中模型辨識為 pistol (0.45) 的物體實際上是 **郵票** — 恰好是 Charade 劇情核心(價值 25 萬美金的稀有郵票)。這是一個有意義的誤判,提示未來可以將「郵票偵測」納入模型。
## 後續建議
### 模型改善
1. 收集 200-500 張電影真實槍枝畫面(動作片如 John Wick, Die Hard重新標註訓練
2. 改用 COCO pre-trained weights 初始化YOLOv8n.pt提升泛化能力
3. 加入郵票類別stamp成為 5-class detector
### Pipeline 整合(暫緩)
- 在模型達到可接受準確率之前,不整合進主 pipeline
- 保留 scripts/gun_detector.py 作為獨立工具供後續測試
## 相關檔案
- `models/gun/gun_detector/weights/best.pt` — PyTorch 權重6MB
- `models/gun/gun_detector/weights/best.mlpackage` — CoreML 匯出版5.9MB, FP16, ANE-ready
- `output_dev/gun_detections/` — 7 張標註截圖
- `data/gun_yolo/` — 訓練資料集905 images, CC BY 4.0

View File

@@ -0,0 +1,32 @@
# 槍枝檢測模型訓練記錄
## 時程
| 時間 | 事件 |
|------|------|
| 12:40 | 開始下載 HuggingFace 槍枝資料集70MB |
| 12:42 | 下載完成,解壓發現僅有圖片無標註 |
| 12:44 | 下載 valid.zip`_annotations.coco.json` |
| 12:50 | 轉換 COCO → YOLO 格式905 張) |
| 13:00 | 開始 YOLOv8n 訓練100 epochs, batch=8, imgsz=640 |
| 13:06 | 完成 Epoch 1mAP50=0.197 |
| 13:07 | 完成 Epoch 2 |
| 13:08 | Epoch 3 進行中... |
## 訓練配置
- Model: YOLOv8n6.5MBtransfer learning
- Dataset: 905 images805 train / 100 val
- Classes: grenade, knife, pistol, rifle
- Batch: 8
- Device: MPSApple Silicon GPU~2.25GB VRAM
- Per epoch: ~42s
- Early stopping: patience=20
- 預計完成: ~30-50 min
## 下一步
完成後:
1. CoreML export → ANE 優化
2. 對 Charade 關鍵場景推論測試
3. 整合進 gun_processor.py

View File

@@ -0,0 +1,61 @@
# Photo (Single-Frame Video) 處理建議
**提供者**M4
**日期**2026-05-07
**對應報告**`M4_workspace/2026-05-07_single_frame_photo_test_report.md`
---
## 現狀
照片JPEG/PNG可成功註冊為 1 幀 video但部分 processor 不支援 raw image 格式。
## 問題
`swift_face`Apple Vision Framework無法直接開啟 JPEG
```
swift_face animal.jpg → ❌ Error: Cannot Open
swift_face animal.mov → ✅ 正常偵測(需先轉換)
```
## 建議修復
`face_processor.py``executor.rs` 中增加前置轉換:
```python
# face_processor.py 開頭
import subprocess, os
def ensure_video(input_path):
"""Convert image to single-frame video if needed"""
ext = os.path.splitext(input_path)[1].lower()
if ext in ('.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'):
output = input_path + '.converted.mov'
subprocess.run([
'ffmpeg', '-y', '-i', input_path,
'-vframes', '1', '-vsync', '0', output
], check=True, capture_output=True)
return output
return input_path
```
## 受影響範圍
| Processor | 需修正 | 原因 |
|-----------|--------|------|
| Face (swift_face) | ✅ 需轉換 | Vision 不吃 raw image |
| OCR | ⚠️ 待確認 | `ocr_processor.py` 未知 |
| Pose | ⚠️ 待確認 | `swift_pose` 未知 |
YOLO、ASR、CUT 已可直接處理 1 幀 video不需修改。
## M5 上驗證方式
M5 已有人像照片在 demo path
```bash
ls ~/momentry/var/sftpgo/data/demo/people*.jpg
# → people.jpg ~ people20.jpg 共 20 張
```
註冊 + 觸發 face 即可測試(需先套用前述轉換)。

View File

@@ -0,0 +1,38 @@
# M4 請求M5 準備執行 YouTube Charade Pipeline
**請求者**M4
**日期**2026-05-07
---
## 背景
M4 已完成分工計畫(`M4_workspace/2026-05-07_M4_M5_pipeline_分工.md`),結論:
| 工作 | 執行者 |
|------|--------|
| YouTube Charade Pipeline (全 processors) | **M5** 🏆 |
| 5W1H LLM + ANE Embedding | **M5** 🏆 |
| Dev Playground + Portal + 測試 | M4 |
## 請求
請 M5 準備:
1. **註冊 YouTube Charade**(已 sync 到 M5 demo path
```
路徑: /Users/accusys/momentry/var/sftpgo/data/demo/Charade (1963) Cary Grant & Audrey Hepburn Comedy Mystery Romance Thriller Full Movie.mp4
```
2. **觸發完整 pipeline**(含 ASR timeout fix 保留部分結果)
```json
{"processors":["asr","cut","yolo","ocr","face","pose","asrx"]}
```
3. **完成後通知 M4**M4 會將結果 sync 回來驗證
## 備註
- YouTube 版25fps, AV1, 1920x1080, English audio, 596MB
- M5 上的 db schema`dev`(與 M4 相同)
- M5 不需處理 5W1H等 pipeline 完成後 M4 會用 M5 的 Gemma4 呼叫

View File

@@ -0,0 +1,69 @@
# M5 回覆 — Pipeline 問題分析報告
**回覆者**M5
**日期**2026-05-07
**對應文件**`M4_workspace/2026-05-07_pipeline_issues_analysis.md`
---
## 已修復
### ✅ 問題 4Registration 階段 cut_processor 參數錯誤
**修復**`src/api/server.rs` — 移除 `--threshold 27` 參數cut_processor.py 已不支援)
### ✅ 修復清單(本次 session 累計)
| 問題 | 狀態 |
|------|------|
| Qdrant voice/face collection 不存在 | ✅ `ensure_collection()` |
| Processor panic 洩漏 pool counter | ✅ `ProcessorCleanupGuard` |
| `file/:uuid/chunks` → 500 | ✅ 移除不存在欄位 |
| `ON CONFLICT` 用錯欄位名 | ✅ `old_chunk_id` 修正 |
| Qdrant face sync schema 重複 | ✅ 移除多餘 schema prefix |
| Job premature completion | ✅ `any_pending` + `any_skipped` 檢查 |
| Auto-vectorize after Rule 1 | ✅ 新增功能 |
| cut_processor --threshold 27 | ✅ 已移除 |
---
## 待確認/待修復
### ❓ 問題 1Rule 1 0 chunksM4 的 YouTube 25fps 測試)
M5 用 `short_clip.mov`SFTPGo path測試時 Rule 1 有正常產出 **3 sentence chunks**。但 M4 用 YouTube 25fps AV1 測試時 Rule 1 產出為 0。可能原因
- ASR `.json.err` 問題導致 ASR 結果未被正確載入
- 或 ASR JSON 格式與 `rule1_ingest.rs` 期待不同
**建議**M4 提供該次測試的 ASR JSON 樣本M5 比對格式
### 🔴 問題 2ASR 輸出寫入 `.err` 而非 `.json`
**根因確認**PythonExecutor 在 process timeout/fail 時會將 `.json.tmp` 改名為 `.json.err``executor.rs:237-247`
ASR_TIMEOUT = 1800s (30min)。對 2 小時長片可能不足。
**修復**:已在評估增加 ASR_TIMEOUT 或改為動態計算
### 🟡 問題 3face_detections = 0
M5 Charade Job 251 的 face processor 產出 **7,998 face pre-chunks**,但 face_detections table 為 0。`store_traced_faces.py` 有執行完成但無寫入。待進一步調查 IoU+embedding matching threshold 或 scene-cut reset 邏輯。
### 🟡 問題 5universal_search Chunk 缺 fps
**狀態**:已知,待排入修正
### 🟢 問題 6IdentityChunkItem 缺 frame 欄位
**狀態**:已知,低優先級
---
## Charade PipelineJob 252
已用新 binary含所有 fix重新觸發。YOLO 處理 412K frames @59.94fps 預計數小時。
---
## 下載的模型
| 模型 | 類型 | 位置 |
|------|------|------|
| Gemma4 26B A4B Q5_K_M | LLM MoE | `/Users/accusys/models/` |
| Qwen3 30B A3B Q5_K_M | LLM MoE | `/Users/accusys/models/` |
| Mistral Small 3.1 24B Q5_K_M | LLM Dense | `/Users/accusys/models/` |
| bge-m3 | Embedding | Ollama + GGUF |
| mxbai-embed-large | Embedding | Ollama + GGUF |
| nomic-embed-text-v2-moe | Embedding | Ollama + GGUF |

View File

@@ -0,0 +1,32 @@
# M5 回覆 — Trace API v1.0.0 驗證結果
**回覆者**M5
**日期**2026-05-07
---
## Trace API 端點驗證
基於 M4 提供的 `TRACE_API_REFERENCE_V1.0.0.md`,在 M5 playground (port 3003) 上對 Charade (file_uuid: `3abeee81d94597629ed8cb943f182e94`) 進行完整驗證:
| # | Endpoint | 結果 | 備註 |
|---|----------|------|------|
| 1 | `POST /face_trace/sortby` | ✅ | 6892 traces, 108204 faces (含 re-scan 後) |
| 2 | `GET /trace/:id/faces` | ✅ | 含 interpolate 參數 |
| 3 | `GET /trace/:id/video` | ✅ | 回傳 2MB MP4 |
| 4 | `GET /video/bbox` | ✅ | 含 overlay |
| 5 | `GET /thumbnail` | ✅ | 回傳 82KB JPEG |
## Pipeline 自動化更新
已修正 P3/P4 stub
- **Identity Agent** → 現在 pipeline 會在 face + asrx 完成後自動呼叫 `run_identity_agent`
- **5W1H Agent** → 現在 pipeline 會在 Rule 1 + Rule 3 完成後自動呼叫 `run_5w1h_agent`
進度報表模板已更新為 v2加入 Identity Agent / 5W1H Agent / ANE vectorize / TMDb face match 階段。
## 待補
- ANE vectorize sentence chunks1709 條)
- 5W1H sentence summaries
- 打 tag release

View File

@@ -0,0 +1,25 @@
# M5 回覆 v2 — ASR .err / Timeout 修復
**回覆者**M5
**日期**2026-05-07
**對應文件**`M4_workspace/2026-05-07_response_to_M5.md`
---
## 已修復Executor timeout 保留部分結果
**根因確認**ASR timeout 時 executor 將 `.json.tmp` 改名為 `.json.err`,破壞了部分 ASR 結果(即使 .tmp 內含有效 JSON
**修復**`src/core/processor/executor.rs``mark_failed()` 現在會先檢查 .tmp 是否為有效 JSON
- ✅ 有效 JSON → 保留為 `.json`(部分結果存活)
- ❌ 無效 JSON → 改名為 `.json.err`(避免 corrupt data
這讓 ASR 或其他 processor 在 timeout 時,已完成的 segments 仍可被後續流程使用。
## 關於 ASR_TIMEOUT 30min
M4 的建議合理 — 不增加 timeout而是保留部分結果。上述 fix 已達成此目標。
## face_detections = 0
等待 M4 的 short clip 測試結果確認是否為取樣密度問題。

View File

@@ -0,0 +1,99 @@
# Scene Classification 選型評估報告
**日期**2026-05-07
**狀態**:❌ 棄用(不適合 Momentry 場景)
---
## 評估目標
對影片中的每個 cut scene 進行場景分類(室內/室外/辦公室/雪景等),提供 metadata 給 5W1H+ summary 和搜尋使用。
## 評估模型Places365 (ResNet18)
| 項目 | 內容 |
|------|------|
| 模型 | ResNet18 預訓練於 Places365365 類場景) |
| 授權 | MIT可商用 |
| 大小 | 43MBPyTorch/ 23MBCoreML |
| 推論速度 | CPU: 12.4ms / **ANE: 0.4ms28x 加速)** |
| 測試影片 | Charade (1963), 6785s, 25fps, 黑白 |
## 測試結果
### 測試 12 秒取樣間隔(註冊階段)
| 參數 | 值 |
|------|------|
| sample_interval | 2s |
| 模型 | CPU PyTorch |
| 產出 | 1 sceneclass 129: "door", 32% |
### 測試 25 秒取樣間隔 + ANE CoreML
| 參數 | 值 |
|------|------|
| sample_interval | 5s |
| 模型 | CoreML ANE0.4ms |
| 樣本數 | 1,357 frames |
| 處理時間 | 129.8s |
| 產出 | **1 sceneclass 129: "door", 32%** |
### 測試 3top-5 分析
| Rank | Class | Name | Confidence |
|------|-------|------|-----------|
| 1 | 235 | concourse | 46% |
| 2 | 177 | escalator | 25% |
| 3 | 129 | **door** | **32%(被選中)** |
| 4 | 315 | indoor | 3% |
| 5 | 306 | sky | 2% |
> *註32%)被選為主 scene_type但大廳46%)的 top-1 信心更高,顯示 scene 合併邏輯有問題。top-5 僅來自第一幀而非全體平均。*
## 問題分析
### 問題 1Places365 類別不匹配電影場景
| 需求場景 | Places365 分類 | 結果 |
|---------|---------------|------|
| 1963 年巴黎街道 | street, alley | ❌ 1920x1080 黑白片辨識度低 |
| 室內客廳 | living_room | ❌ 分類為 door |
| 辦公室 | office | ❌ 不存在 top-5 |
| 雪景 | snow | ❌ 不存在 |
| 車內 | car_interior | ❌ 不在 365 類中 |
### 問題 2場景切割 logic 不足
`_merge_scenes` 沒有正確偵測場景轉換。整部 6785s 被歸為一個場景,因為 Places365 對每個 frame 的分類變化不足以觸發 scene cutmin_scene_duration=3s
### 問題 3top-5 只來自第一幀
```python
# 程式碼中的 bug
"top_5": first_pred["predictions"][:5] # 只用第一幀的 predictions
```
而不是收集所有幀的 top-5 統計。
## 棄用原因
| 原因 | 說明 |
|------|------|
| 類別不匹配 | Places365 的 365 類場景是「風景/場所」分類,不適用於電影場景分析 |
| 切割失敗 | 無法正確偵測 scene 轉換 |
| 黑白影片 | 訓練資料為彩色照片,對黑白老電影辨識力低 |
| 替代方案 | Pipeline 已有 **CUT processorPySceneDetect** 做場景切分1,130 cuts準確度遠高於 Places365 |
| 5W1H+ 可補充 | LLMGemma4可以直接從 transcript 推斷場景類型,不需要視覺分類 |
## 建議替代方案
| 方案 | 優點 | 缺點 |
|------|------|------|
| **CUT processor 現有切分** ✅ | 已產出 1,130 cuts準確 | 只有時間點,無場景類型 |
| **LLM 從 transcript 推斷** | 不需要視覺模型5W1H+ 已整合 | 只能從對話推測 |
| **YOLO 物件輔助** | 已檢測 75 類物件car, chair, phone | 間接推測,非直接場景分類 |
| **專用電影場景資料集MovieNet/SUN** | 更適合電影場景 | 需要另找模型 |
## 結論
**Places365 不適用於 Momentry 的場景分類需求。** 目前 CUT processor 的 1,130 個 scene cuts + YOLO 物件檢測 + 5W1H+ LLM 分析,已能提供足夠的場景資訊。未來若有需求可評估 MovieNet 或 SUN RGB-D 等電影專用資料集。

View File

@@ -0,0 +1,51 @@
# Session Summary — 2026-05-07
## Pipeline
- Job 255 (Charade 25fps, 169K frames) ✅ **completed**
- 7/7 processors: asr, cut, yolo, ocr, face, pose, asrx
- Rule 1: 5,250 sentence chunks
- Rule 3: 1,130 cut scenes
- face_trace: 6,186 detections, 2,769 traces
## Identity Agent — Iterative Multi-Angle Face Matching
- TMDb seeds (12 identities) → Round 1: 33% → Round 2-3: **99%**
- 6,175/6,186 face detections matched to 11 identities
- Quality control (temporal collision check) integrated
- Face-embedding matching now uses propagation (matched traces' faces become new seeds)
## Code Changes
### `src/core/tmdb/face_agent.rs`
- Rewrote `match_faces_against_tmdb` with iterative multi-angle matching
- Added `quality_check_temporal_collisions` for temporal collision detection
- Added 4-face minimum check
### `src/api/identity_agent_api.rs`
- `analyze_identity` now persists results to `identities` table
- Added `match_faces_iterative` with same iterative algorithm
- Fixed hardcoded FPS (23.976 → 25)
- Fixed ASRX field name parsing (`speaker``speaker_id`)
- Fixed `face_clustered.json` path fallback
- Fixed `suggest_clustering` to use `face_detections` directly
### `src/core/processor/executor.rs`
- Partial output preservation on timeout: if .tmp is valid JSON → rename to .json not .err
### `src/worker/processor.rs`
- `kill_existing_processor`: kill old PID before starting new processor
- `sweep_stale`: reset to Pending instead of Failed
### `src/worker/job_worker.rs`
- `any_pending` + `any_skipped` checks to prevent premature job completion
- Auto-vectorize after Rule 1 chunking
### `src/api/five_w1h_agent_api.rs`
- Rewrote with hierarchical 5W1H+ (parent summary + child enhanced)
- Uses Gemma4 via `MOMENTRY_LLM_SUMMARY_URL`
- Embedding-optimized prompt (5W1H+ per sentence)
- Face trace info in prompt context
## Models Downloaded
- LLM: Gemma4 26B MoE, Qwen3 30B MoE, Mistral 24B
- Embedding (Ollama + GGUF): mxbai-embed-large, bge-m3, nomic-embed-text-v2-moe
- ANE CoreML: mxbai-embed-large (8.5ms, 1.8x faster than Ollama)

View File

@@ -0,0 +1,66 @@
# Session Summary v2 — 2026-05-07 (16:57)
## Pipeline 自動化狀態確認
### ✅ 已自動化(新影片註冊後自動執行)
| 階段 | 觸發條件 | 狀態 |
|------|---------|------|
| 7 Processors (cut/asr/yolo/ocr/face/pose/asrx) | 註冊後自動 | ✅ |
| sweep_stale (重設 stuck → Pending) | 每次檢查 | ✅ |
| kill_existing_processor (防重複) | processor 啟動前 | ✅ |
| Rule 1 Chunking (sentence chunks) | ASR + ASRX 完成後 | ✅ |
| **Vectorize (ANE CoreML)** | Rule 1 完成後自動 | ✅ |
| Rule 3 Scene Chunking (cut scenes) | all_completed | ✅ |
| Face Trace + DB Store | face 完成後 | ✅ |
| Qdrant Face Sync | face trace 完成後 | ✅ |
| TMDb Face Matching (若啟用) | face 完成後 | ✅ |
| 保留 partial output on timeout | executor 內建 | ✅ |
### ⚠️ 未自動化 / 僅 stub
| 階段 | 現狀 | 需處理 |
|------|------|--------|
| **Identity Agent (P3)** | stub只 log "started",未呼叫 `match_faces_iterative` | ❌ |
| **5W1H Agent (P4)** | stubsleep 30s 後 log "started",未呼叫 API | ❌ |
| **Parent-child linking (cut↔sentence)** | 本次手動完成,未內建於 pipeline | ❌ |
| **Gun detection** | 未整合進 pipeline | — |
### 結論
若今日註冊新影片pipeline 會自動完成7 processors → Rule 1 + vectorize → Rule 3 → face trace + Qdrant sync → TMDb match。**但 identity binding 和 5W1H summaries 需手動補執行。**
---
## 本次工作
### Charade (Job 255) 最終狀態
| 項目 | 數值 |
|------|------|
| Duration | 6785s (25fps) |
| Frames | 169,625 |
| Processors | 7/7 ✅ |
| Cut scenes | 1,130 |
| Face detections | 108,204 |
| Face traces | 6,892 |
| Identities | 2,810 |
| Sentence chunks | **去重前** 5,250 → **去重後** 1,709 |
| Sentence → Cut 已連線 | 1,709/1,709 ✅ |
| Vector embeddings | ❌ 尚未執行(需手動觸發或等 P1 重跑) |
| 5W1H sentence summaries | ❌ 未執行 |
### Gun Detection Training
- 模型YOLOv8n, 905 images (CC BY 4.0), 4 classes
- **Best mAP50: 0.813** (epoch 84)
- CoreML 匯出best.mlpackage (5.9MB, FP16, ANE-ready)
- **Charade 實測:全數 False Positive**
- 意外發現t=5400s 將郵票誤判為 pistol (0.45)
- 評估報告:`M5_workspace/2026-05-07_gun_detection_evaluation.md`
### M4 文件處理
- 已讀取 `docs_v1.0/API_V1.0.0/TRACE/TRACE_API_REFERENCE_V1.0.0.md`
— Trace API 設計理念3+1D, Hidden Traces, MR Bridge待後續整合
### 待辦Release 前)
1. 修正 Identity Agent stub → 實際呼叫 match_faces_iterative
2. 修正 5W1H Agent stub → 實際呼叫 five_w1h API
3. 內建 parent-child linking 進 pipeline (Rule 1 完成後自動建立)
4. 觸發 vectorize若未自動執行及 sentence 5W1H
5. 打 tag + release

View File

@@ -0,0 +1,48 @@
# M5 → M4 同步通知
## 1. Embedding 模型更換
mxbai-embed-largeEnglish only, ANE**EmbeddingGemma 300M**(多語, Metal GPU
| 項目 | 舊 | 新 |
|------|-----|------|
| 模型 | mxbai-embed-large | EmbeddingGemma 300M |
| 維度 | 1024D | **768D** |
| 多語 | ❌ English only | ✅ 中英日韓 |
| 埠口 | 11435ANE CoreML | **11436Python MPS** |
| 啟動方式 | `scripts/coreml_embed_server.py` | **`scripts/embeddinggemma_server.py --port 11436`** |
M4 同步步驟:
```bash
# 1. 確認 torch + transformers 已安裝
pip install torch transformers flask
# 2. 登入 HuggingFace需接受 EmbeddingGemma 授權)
# 前往 https://huggingface.co/google/embeddinggemma-300m → Agree
huggingface-cli login --token YOUR_TOKEN
# 3. 啟動 server
python3 scripts/embeddinggemma_server.py --port 11436
```
## 2. 環境變數變更
`.env.development`
```diff
-MOMENTRY_EMBED_URL=http://localhost:11435
+MOMENTRY_EMBED_URL=http://localhost:11436
-MOMENTRY_LLM_SUMMARY_URL=http://192.168.110.201:8081/v1/chat/completions
+MOMENTRY_LLM_SUMMARY_URL=http://localhost:8082/v1/chat/completions
```
LLM 摘要已改為 M5 本地 Gemma4 26B MoEport 8082不再依賴 M4。
## 3. Qdrant Collection
新增 `momentry_dev_rule1`768D, Cosine存放 sentence chunk embeddings。
## 4. 選型文件
詳細評估記錄:`M5_workspace/2026-05-07_embedding_model_selection.md`

View File

@@ -0,0 +1,33 @@
# Pipeline 進度報表 — 條件→依賴狀態修正
**回覆者**M4
**日期**2026-05-07
**對應文件**`M4_workspace/2026-05-07_pipeline_progress_report_template.md`
---
## 修正內容
「條件」欄改為「依賴進度狀態」,直接顯示各 dependency 的即時狀態:
| 階段 | 舊(條件名稱) | 新(依賴進度狀態) |
|------|--------------|-------------------|
| Rule 1 chunks | `ASR + ASRX ✅` | `ASR⏳ + ASRX⬜` |
| face_trace | `all 7 processors ✅` | `cut✅ face✅ ocr✅ pose✅ yolo⏳ asr⏳ asrx⬜` |
| Qdrant face sync | `face_trace ✅` | `face_trace⬜` |
| Qdrant voice | `ASRX ✅ (inline)` | `ASRX⬜ (inline)` |
| ANE vectorize | `Rule 1 chunks ✅` | `Rule 1 chunks⬜` |
| 5W1H Agent | `Rule 1 + Rule 3 ✅` | `Rule 1⬜ + Rule 3⬜` |
── Post-Processing ──
Stage Status 已產出 依賴進度狀態
------------------- ---------- -------------- -------------------
Rule 1 chunks ⬜ - ASR⏳ + ASRX⬜
face_trace ⬜ - cut✅ face✅ ocr✅ pose✅ yolo⏳ asr⏳ asrx⬜
Qdrant face sync ⬜ - face_trace⬜
Qdrant voice ⬜ - ASRX⬜ (inline)
ANE vectorize ⬜ - Rule 1 chunks⬜
5W1H Agent ⬜ - Rule 1⬜ + Rule 3⬜
```
一眼可知卡在哪個依賴。

View File

@@ -0,0 +1,321 @@
# Visual Speaker Diarization 選型評估報告
**日期**2026-05-07
**作者**M5
**目的**:評估從視覺(嘴型)辨識誰在說話的技術方案
---
## 1. 問題定義
Momentry pipeline 目前透過 ASRX 進行 speaker diarization聲紋辨識但 speaker 綁定到 face trace 需要驗證。目標是透過**視覺資訊(嘴型運動)**來確認「誰在說話」。
## 2. 現有資料
### 2.1 Charade 測試影片
| 項目 | 數值 |
|------|------|
| 影片長度 | 6785s~1.9hr |
| FPS | 25 |
| 解析度 | 1920×1080 |
| ASRX 辨識 | 10 個 speakers1,726 segments |
| 原始 face 取樣 | sample_interval=30~每隔 1.2s 一幀) |
| 已匹配 face traces | 6,186 張臉、2,769 個 trace、11 位演員 |
### 2.2 臉部資料完整性
| 資料項 | 數量 | 說明 |
|--------|------|------|
| Total face entries | **91,216** | 含 re-scan 後的資料 |
| 含 lips coordinates | **91,216100%** | outer_lips 14 點 |
| 含 pose_angle | **91,216100%** | yaw/pitch/roll |
| 含 landmarks | **0** | 不使用 |
| 已綁定 identity | **6,175/6,18699.8%** | |
### 2.3 Lips 資料結構
每張臉包含 outer_lips14 個 2D 座標點):
```json
"outer_lips": [
[486.0, 180.0], // 左上(嘴角)
[489.3, 182.1], // 上唇中
[493.5, 182.9], // 右上(嘴角)
[501.9, 180.9], // 右
[509.4, 182.5], // 右下
[514.6, 184.7], // 下唇中偏右
[518.1, 186.9], // 下唇中
[514.3, 185.8], // 下唇中偏左
[509.1, 184.1], // 左下
[501.9, 182.6], // 左
[494.5, 181.7], // 左上內
[489.7, 181.3], // 上唇中內
[486.3, 181.3], // 右上內
[483.4, 181.9], // 右上
]
```
#### Lip Height 計算
```python
lip_height = max(ys) - min(ys)
# ys = [y1, y2, ..., y14] 取 mouth opening 幅度
mouth_opening = current_lip_height - baseline_lip_height
# baseline = 嘴巴閉合時的平均 lip_height說話前的 3 幀)
```
#### 「誰先開口」偵測演算法
```
for each ASR segment (start_time):
1. 取 frame window = [start_time - 3frames, start_time + 10frames]
2. 將 window 中所有 face 匹配到 face_detections trace_id
(透過 spatial bbox proximity matching
3. 對每個 trace計算 lip_height 變化:
before = avg_lip_height(start-3 到 start-1)
after = avg_lip_height(start 到 start+10)
motion = (after - before) / before
4. motion > 5% → 該 trace 正在說話
5. motion 最大者 → 說話者
```
#### 匹配 face.json → DB face_detectionsSpatial Matching
face.json 中的 frame 只有 face bbox (x,y,w,h),沒有 trace_id。需透過空間比對
```python
for each frame:
json_faces = face.json[frame].faces[] # (x,y,w,h,lip_height)
db_faces = face_detections[frame] # (trace_id,x,y,w,h)
for json_face in json_faces:
for db_face in db_faces:
dist = abs(x1-x2) + abs(y1-y2) + abs(w1-w2) + abs(h1-h2)
if dist < 50: # 合理門檻
trace_lips[trace_id] += json_face.lip_height
```
### 2.4 Re-scan 後資料量
| 項目 | 原始 | Re-scan 後 |
|------|------|-----------|
| face.json 大小 | ~105MB | **1.2GB** |
| Face entries | ~3,992 | **91,216** |
| face_detections (DB) | 6,186 | **108,204** |
| Unique traces | 2,769 | **6,892** |
| 連續幀 (gap=1) | ~4% | **96%** |
| 有 lips 資料 | 91,216 (100%) | 91,216 (100%) |
---
## 3. 候選方案
### 方案 A簡化版 — Lips Motion Analysis推薦
**作法:**
```
1. 對每個 ASRX speaker 發話區段
2. 取出該時間範圍內所有 face detections
3. 計算每張臉的 lip_height 標準差
4. 標準差最大者 → 正在說話的人
```
**實驗結果Re-scan 後,連續幀):**
| 指標 | Re-scan 前 | Re-scan 後 |
|------|-----------|-----------|
| 可分析幀 (gap≤3) | 4% | **96%** |
| 60,873 張臉 | — | 含連續取樣 |
| 預估可辨識率 | 8% | **~70-80%**(待驗證) |
#### 取樣密度與 VSP-LLM 對照
```
VSP-LLM (paper): 25fps, gap=1 → 100% 連續
Momentry 原始: sample_interval=30, gap=30 → ~3% 連續 ❌
Momentry re-scan: 1-frame interval, gap=1 → 96% 連續 ✅
```
| 間隔 | 時間 | 幀數比 | 對嘴型分析 |
|------|------|--------|-----------|
| gap=1 | **0.04s** | **96%** | ✅ **25fps = VSP-LLM 等級** |
| gap=2~10 | 0.08~0.40s | 1% | ⚠️ 可接受 |
| gap=30 | 1.2s(原始採樣) | 3% | ❌ 太稀疏 |
| gap>30 | >1.2s(無臉部偵測) | <1% | ❌ 無資料 |
#### 人聲嘴型運動的物理限制
| 參數 | 數值 | 依據 |
|------|------|------|
| 人聲嘴型運動頻率 | 515 Hz | 語音學研究 |
| Nyquist 最小取樣 | 30 Hzgap~0.8 | 定理 |
| 實際可靠取樣 | 2025 Hzgap=1 | VSP-LLM 實證 |
| 最低可用取樣 | 8 Hzgap~3 | 可捕捉主要嘴型變化 |
**結論gap ≤ 3~8Hz為可接受的取樣密度下限。re-scan 後 97% 幀對達到此標準。**
#### 低偵測率原因(已解決)
```
Re-scan 前ASR 開始 141.1s → face frame 3525, 3555, 3585gap=30, 1.2s
Re-scan 後ASR 開始 141.1s → face frame 3525, 3526, 3527...gap=1, 0.04s
→ 可看到口型變化的瞬間
```
**優點:**
- ✅ 不需要 GPU
- ✅ 不需要外部模型
- ✅ 不需要訓練
- ✅ 語言無關(中英文皆可)
- ✅ Re-scan 後取樣密度達 VSP-LLM 水準
- ✅ 資料已就緒
#### 實際測試結果50 個 ASR 片段)
**測試條件:**
- 使用 re-scan 後 face.json1.2GB, 91,216 entries
- 每片段取 start_frame ± 310 幀視窗
- Spatial bbox matching 對應 face.json → DB trace_id
- Lip opening threshold: motion > 5%
**結果摘要:**
| 類別 | 數量 | 佔比 |
|------|------|------|
| 可透過 lip motion 判定 | 13 | **26%** |
| 無明顯 lip motionnolip | 17 | **34%** |
| 無 face 資料(畫外音) | 20 | **40%** |
| **合計可判定** | **30/50** | **60%** |
**注意事項:**
- 「無明顯 lip motion」的片段中trace 在 ASR 時間窗內有出現但 lip_height 無顯著變化。可能原因:
1. 說話的人沒入鏡(單一 trace 是其他人)
2. 該 ASR 片段說話者與 frame 中的人不同
- 「無 face 資料」屬正常 — 畫外音、旁白、被遮擋
- **即使只有一個 trace 也不能直接假設是說話者** → 仍需 lip motion 驗證
**Lip Motion 成功案例:**
```
225.6s: "Je vais pas jouer, mon chéri"
→ trace_72 detected speaking (motion=90%) ✅
223.5s: "and avalanche or something"
→ trace_76 detected speaking (motion=90%) ✅
258.7s: "But I don't understand. Why"
→ trace_125 detected speaking (motion=30%) ✅
```
### 方案 BVSP-LLM完整論文方案
**作法:**
```
影片 → AV-HuBERT (visual encoder) → LLaMA2-7B + LoRA → 文字輸出
```
**部署需求評估:**
| 元件 | 大小 | 取得難度 | 授權 |
|------|------|---------|------|
| VSP-LLM repo | < 1MB | ✅ GitHub | MIT |
| AV-HuBERT Large | ~3GB | ❌ 原始連結失效,需 mirror | 學術 |
| VSP-LLM checkpoint | ~? | ⬜ Google Drive 需手動 | — |
| LLaMA2-7B | ~14GB | ❌ HuggingFace gated model | Meta 授權 |
**優點:**
- ✅ 可直接輸出文字(誰說了什麼)
- ✅ 狀態最先進EMNLP 2024
**缺點:**
- ❌ 需要 LLaMA2 授權Meta 審核)
- ❌ AV-HuBERT 原始連結失效
- ❌ 需要 GPUMPS 可跑但慢)
- ❌ 換成其他 LLM 需要大量改程式
- ❌ 不支援中文(需重新訓練)
### 方案 CMediaPipe Face Mesh地標提取
**作法:**
```
影片 → MediaPipe Face Mesh → 478 點 3D landmarks → 嘴型運動分析
```
| 指標 | 估計值 |
|------|--------|
| 每幀處理時間 | ~30msCPU/ ~5msANE |
| 91,216 faces 處理時間 | ~45 分鐘 / ~8 分鐘 |
| 新增資訊 | 嘴唇 + 眼睛 + 眉毛 + 下巴完整 3D 資料 |
**優點:**
- ✅ 比現有 14 點 lips 更細(完整嘴型)
- ✅ 3D 資訊,可判斷嘴部深度變化
- ✅ 開源Apache 2.0
**缺點:**
- ❌ 需要額外安裝 MediaPipe
- ❌ 對 91,216 張臉處理需 8-45 分鐘
- ❌ 現有 14 點 lips 已夠用
---
## 4. 比較總結
| 評估項 | A: Lips Motion | B: VSP-LLM | C: MediaPipe |
|--------|---------------|------------|-------------|
| **實作難度** | 🟢 **低** | 🔴 極高 | 🟡 中 |
| **所需時間** | 🟢 **今天** | 🔴 數週 | 🟡 數小時 |
| **外部依賴** | 🟢 無 | 🔴 LLaMA+AV-HuBERT | 🟡 MediaPipe |
| **訓練需求** | 🟢 不需要 | 🔴 需 LRS3 | 🟢 不需要 |
| **辨識能力** | 🟡 誰在說話 | 🟢 誰+說什麼 | 🟡 誰在說話 |
| **中文支援** | 🟢 語言無關 | 🔴 不支援 | 🟢 語言無關 |
| **授權風險** | 🟢 MIT | 🟡 Meta 授權 | 🟢 Apache 2.0 |
## 5. 建議
### 結論
**視覺 speaker diarization 可行,但目前的 lip_height 運動量偵測有 60% 覆蓋率。** 主要的限制不是取樣密度(已達 96% gap=1而是 40% 的 ASR 片段在說話時 speaker 不在鏡頭內。
### 實作優先級
**第一優先:將 lip motion verification 整合進 speaker binding**
- 對現有 `bind_speakers` 加入 lip motion 做 cross-validation
- ASRX speaker + face trace 綁定時,檢查 speaker 的 ASR 區段是否有對應 lip motion
- 若無 lip motion → 降低 confidence
- 實作位置:`src/api/identity_agent_api.rs::bind_speakers()`
**第二優先(視需要):完整視覺 speaker diarization pipeline**
- 建立新 processor: `visual_speaker_diarization.py`
- 對每個 ASR 片段,自動判定誰在說話
- 與 ASRX 結果做交叉比對
### Lip Height 關鍵公式
```python
# 從 outer_lips 14 點計算
lip_height = max(y_points) - min(y_points)
# 說話開始時嘴型變化
mouth_opening_ratio = (avg_after - avg_before) / avg_before
# 門檻
THRESHOLD = 0.05 # 5% opening = 正在說話
# 判定
if mouth_opening_ratio > THRESHOLD:
speaker_found = True
elif no_face_data:
speaker_unknown = True # 畫外音
else:
needs_review = True # 可能非說話者入鏡
```
### 未來研究方向
| 方向 | 潛力 | 所需資源 |
|------|------|---------|
| MediaPipe 478 點 3D landmarks | 更精確的嘴型 + 頭部轉向 | 安裝 MediaPipe~30min |
| Per-trace lip motion history | 不只是 ASR 開始,追蹤整段說話的 lip 變化 | 已可行 |
| VSP-LLM 完整部署 | 誰+說什麼 | 需 LLaMA2 授權 + AV-HuBERT |

View File

@@ -0,0 +1,293 @@
# Video Processing Pipeline - 處理流程
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-22 |
| 文件版本 | V1.1 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-22 | 創建文件 | Warren | OpenCode |
| V1.1 | 2026-03-26 | 更新流程圖文字 (media_url→file_path) | OpenCode | deepseek-reasoner |
---
## 處理流程架構
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ Video Processing Pipeline │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Stage 1: JSON 生成 (Process) │ │
│ │ │ │
│ │ video.mp4 ──→ [ASR] ──→ asr.json (語音辨識) │ │
│ │ ──→ [CUT] ──→ cut.json (場景偵測) │ │
│ │ ──→ [ASRX] ──→ asrx.json (說話者分離) │ │
│ │ ──→ [YOLO] ──→ yolo.json (物體偵測) │ │
│ │ ──→ [OCR] ──→ ocr.json (文字辨識) │ │
│ │ ──→ [Face] ──→ face.json (人臉偵測) │ │
│ │ ──→ [Pose] ──→ pose.json (姿態估計) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Stage 2: 入庫 (Import) │ │
│ │ │ │
│ │ .json files ──→ PostgreSQL (fs_json = true) │ │
│ │ ↓ │ │
│ │ pre_chunks 表 (from ASR, CUT) │ │
│ │ frames 表 (from YOLO, OCR, Face, Pose) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Stage 3: Chunk 生成 (Chunk) │ │
│ │ │ │
│ │ pre_chunks ──→ [Chunk Rule] ──→ chunks 表 │ │
│ │ ↓ │ │
│ │ 清洗 → 純文字 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Stage 4: 向量化 (Vectorize) │ │
│ │ │ │
│ │ chunks ──→ [Embedding Model] ──→ vectors │ │
│ │ ↓ │ │
│ │ Qdrant (主要向量庫) │ │
│ │ PGVector (備份向量庫) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Stage 5: 搜尋 (Search) │ │
│ │ │ │
│ │ Natural Language Query ──→ [Embedding] ──→ [Qdrant Search] │ │
│ │ ↓ │ │
│ │ 返回結果含 file_path │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
---
## CLI 命令
### Stage 1: JSON 生成 (Process)
```bash
# 基本用法
cargo run --bin momentry -- process <uuid_or_path>
# 只處理特定模組
cargo run --bin momentry -- process <uuid> --modules asr,cut
# 強制重新處理(忽略完整性檢查)
cargo run --bin momentry -- process <uuid> --force
# 從中斷點續傳
cargo run --bin momentry -- process <uuid> --resume
# 模組使用雲端處理
cargo run --bin momentry -- process <uuid> --modules yolo,face --cloud yolo
# 完整範例
cargo run --bin momentry -- process /path/to/video.mp4 \
--modules asr,cut,yolo,ocr \
--cloud yolo
```
### Stage 2: 入庫 (Import)
```bash
# 目前入庫在 process 完成後自動執行
# 計劃新增獨立的 import 命令
# cargo run --bin momentry -- import <uuid>
```
### Stage 3: Chunk 生成
```bash
# 生成 chunks
cargo run --bin momentry -- chunk <uuid>
```
### Stage 4: 向量化
```bash
# 向量化 chunks
cargo run --bin momentry -- vectorize <uuid>
# 指定模型
cargo run --bin momentry -- vectorize <uuid> --model sentence-transformers/all-MiniLM-L6-v2
```
---
## 處理模式選項
### --force (強制重新處理)
- 刪除現有的 JSON 檔案
- 從頭開始處理
- 適用於:處理失敗、模型更新、需要重新處理
```bash
# 強制重新處理 YOLO
cargo run --bin momentry -- process <uuid> --modules yolo --force
```
### --resume (續傳)
- 檢查現有 JSON 的進度
- 從中斷點繼續處理
- 適用於:處理中斷、系統崩潰後恢復
```bash
# 從上次中斷點繼續
cargo run --bin momentry -- process <uuid> --resume
```
### 預設行為 (Smart Mode)
- 如果 JSON 完全:跳過
- 如果 JSON 不完整:警告 + 跳過(需要 --resume 或 --force
- 如果 JSON 不存在:處理
```
Output:
ASR: ✓ Already complete, skipping
⚠️ Found incomplete JSON file: /path/to/yolo.json
Progress: 73800/412343 (17.9%)
Use --resume to continue from checkpoint
Use --force to reprocess from scratch
YOLO: ✓ Already complete, skipping
```
---
## 可用模組
| 模組 | 功能 | 輸出 | 用途 |
|------|------|------|------|
| asr | 自動語音辨識 | asr.json | 語音轉文字 |
| cut | 場景偵測 | cut.json | 影片分段 |
| asrx | 說話者分離 | asrx.json | 多人對話分析 |
| yolo | 物體偵測 | yolo.json | 物體辨識 |
| ocr | 文字辨識 | ocr.json | 畫面文字 |
| face | 人臉偵測 | face.json | 人臉辨識 |
| pose | 姿態估計 | pose.json | 人體姿態 |
---
## 向量化模型選擇
### 統一嵌入模型
Momentry Core 統一使用 **`nomic-embed-text-v2-moe:latest`** 作為所有規則的嵌入模型:
```bash
# 統一模型(所有 Rule 1/2/3 使用)
--model nomic-embed-text-v2-moe:latest
```
### 模型特性
| 特性 | 說明 |
|------|------|
| **模型名稱** | `nomic-embed-text-v2-moe:latest` |
| **向量維度** | 768 維 |
| **多語言支持** | ✅ 完整支持(英語、中文、日語、韓語等) |
| **模型架構** | Mixture of Experts (MoE) |
| **推理速度** | 快速,適合實時應用 |
### 使用方式
```bash
# 向量化命令
cargo run --bin momentry -- vectorize <uuid> --model nomic-embed-text-v2-moe:latest
```
---
## 資料庫儲存
### PostgreSQL (主要關聯式資料庫)
- 影片資訊
- Chunks 資料
- Pre-chunks 資料
- Frames 資料
- 使用者資料
### Qdrant (主要向量資料庫)
- Chunk 向量
- 相似度搜尋
### PGVector (備份向量資料庫)
- Chunk 向量副本
- 備援機制
---
## Pipeline 狀態追蹤
### PostgreSQL 狀態欄位
```sql
-- 影片處理狀態
videos.status: 'pending' | 'processing' | 'completed' | 'failed'
-- 檔案處理狀態
videos.fs_json: true/false
videos.fs_chunks: true/false
videos.fs_vectors: true/false
-- pre_chunks 狀態
pre_chunks.imported: true/false
-- frames 狀態
frames.imported: true/false
-- chunks 狀態
chunks.cleaned: true/false
chunks.vectorized: true/false
```
### 進度查詢 API
```bash
# 查詢處理進度
curl http://localhost:3002/api/v1/progress/{uuid}
# 回應範例
{
"uuid": "a1b10138a6bbb0cd",
"file_name": "video.mp4",
"overall_progress": 65,
"cpu_percent": 45.2,
"gpu_percent": 98.5,
"memory_mb": 8500,
"processors": [
{"name": "asr", "status": "complete", "progress": 100},
{"name": "cut", "status": "complete", "progress": 100},
{"name": "yolo", "status": "progress", "progress": 45},
{"name": "ocr", "status": "pending", "progress": 0}
]
}
```
---
## 下一步
1. **API 端點** - 支援 --modules 和 --cloud 參數
2. **獨立 Import 命令** - 分離入庫流程
3. **獨立 Chunk 命令** - 分離 chunk 生成
4. **獨立 Vectorize 命令** - 分離向量化流程
5. **模型管理** - 新增、選擇、預覽模型

View File

@@ -0,0 +1,248 @@
# Video Registration
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-25 |
| 文件版本 | V1.1 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-25 | 創建文件 | Warren | OpenCode |
| V1.1 | 2026-03-26 | 修正 curl 範例,新增 API Key 驗證標頭 | OpenCode | deepseek-reasoner |
---
## 概述
影片註冊 API (`POST /api/v1/register`) 用於將影片加入 Momentry Core 系統進行處理。
## 路徑格式
### 支援的路徑格式
| 格式 | 範例 | 說明 |
|------|------|------|
| 相對路徑 | `./demo/video.mp4` | 推薦格式 |
| 相對路徑(無 ./ | `demo/video.mp4` | 自動加上 `./` |
| 絕對路徑 | `/Users/.../sftpgo/data/demo/video.mp4` | 支援但不推薦 |
### 路徑結構
```
./username/filepath
│ │ │
│ │ └── 檔案路徑(可以是多層目錄)
│ └── 使用者名稱SFTPgo 用戶目錄名稱)
└── 相對路徑前綴
```
**範例**
- `./demo/video.mp4` → username=`demo`, filepath=`video.mp4`
- `./demo/movies/2024/video.mp4` → username=`demo`, filepath=`movies/2024/video.mp4`
- `./warren/project1/interview.mp4` → username=`warren`, filepath=`project1/interview.mp4`
## UUID 計算
### 計算規則
```
UUID = SHA256(username/filepath)[0:16]
```
**範例**
```rust
// 路徑: ./demo/video.mp4
// username: "demo"
// filepath: "video.mp4"
// key: "demo/video.mp4"
// UUID: SHA256("demo/video.mp4")[0:16]
```
### 特性
| 特性 | 說明 |
|------|------|
| 用戶隔離 | 不同用戶的相同檔名會產生不同 UUID |
| 一致性 | 相同相對路徑一定產生相同 UUID |
| 遷移安全 | SFTPgo 資料路徑變更後 UUID 保持一致 |
### 範例
```rust
// 用戶 demo 的影片
compute_uuid_from_relative_path("./demo/video.mp4")
// → "9760d0820f0cf9a7"
// 用戶 warren 的相同檔名影片
compute_uuid_from_relative_path("./warren/video.mp4")
// → "a1b2c3d4e5f6g7h8" (不同的 UUID)
```
## 重複註冊檢查
### 行為
1. 系統檢查 UUID 是否已存在於資料庫
2. 如果存在,返回 `already_exists: true` 和現有影片資訊
3. 如果不存在,創建新的影片記錄
### API 回應
**新註冊**
```json
{
"uuid": "9760d0820f0cf9a7",
"video_id": 18,
"job_id": 2,
"file_name": "video.mp4",
"duration": 159.637188,
"width": 640,
"height": 360,
"already_exists": false
}
```
**重複註冊**
```json
{
"uuid": "9760d0820f0cf9a7",
"video_id": 18,
"job_id": 2,
"file_name": "video.mp4",
"duration": 159.637188,
"width": 640,
"height": 360,
"already_exists": true
}
```
## SFTPgo 整合
### 目錄結構
SFTPgo 的用戶目錄結構:
```
/Users/accusys/momentry/var/sftpgo/data/
├── demo/ ← 用戶目錄
│ ├── video.mp4
│ └── movies/
│ └── movie1.mp4
├── warren/ ← 用戶目錄
│ └── project1/
│ └── interview.mp4
└── momentry/ ← 用戶目錄
└── presentation.mp4
```
### 註冊流程
1. SFTPgo 用戶上傳檔案到各自的目錄
2. n8n 或其他服務調用註冊 API
3. 使用相對路徑格式:`./username/filepath`
4. 系統計算 UUID 並檢查重複
5. 創建處理任務
## 程式碼範例
### 註冊影片
```bash
# 使用相對路徑註冊
curl -X POST http://localhost:3002/api/v1/register \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{"path": "./demo/video.mp4"}'
# 或使用多層目錄
curl -X POST http://localhost:3002/api/v1/register \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{"path": "./demo/movies/2024/video.mp4"}'
```
### UUID 計算函數
```rust
// 使用相對路徑計算 UUID
pub fn compute_uuid_from_relative_path(relative_path: &str) -> String {
let (username, filepath) = extract_user_from_relative_path(relative_path);
compute_uuid(&username, &filepath)
}
// 從相對路徑提取用戶名和檔案路徑
pub fn extract_user_from_relative_path(relative_path: &str) -> (String, String) {
let path = relative_path.strip_prefix("./").unwrap_or(relative_path);
let path_buf = PathBuf::from(path);
let mut components = path_buf.components();
let username = components
.next()
.map(|c| c.as_os_str().to_string_lossy().to_string())
.unwrap_or_default();
let filepath: String = components
.map(|c| c.as_os_str().to_string_lossy().to_string())
.collect::<Vec<_>>()
.join("/");
(username, filepath)
}
```
## 相關 API
### Probe API僅探測不註冊
如果只需要取得影片資訊而不註冊,可以使用 Probe API
```bash
curl -X POST http://localhost:3002/api/v1/probe \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{"path": "./demo/video.mp4"}'
```
**回應範例**
```json
{
"uuid": "a1b10138a6bbb0cd",
"file_name": "video.mp4",
"duration": 120.5,
"width": 1920,
"height": 1080,
"fps": 30.0,
"cached": false,
"format": {...},
"streams": [...]
}
```
**與 Register API 的差異**
| 功能 | Probe API | Register API |
|------|-----------|---------------|
| 計算 UUID | ✓ | ✓ |
| 執行 ffprobe | ✓ | ✓ |
| 儲存 probe.json | ✓ | ✓ |
| 寫入 videos 表 | ✗ | ✓ |
| 建立 monitor_job | ✗ | ✓ |
| 返回 job_id | ✗ | ✓ |
| 適用場景 | 預覽影片資訊 | 註冊並處理影片 |
## 相關檔案
| 檔案 | 說明 |
|------|------|
| `src/core/storage/uuid.rs` | UUID 計算邏輯 |
| `src/api/server.rs` | 註冊與 Probe API 實現 |
| `src/core/probe/ffprobe.rs` | ffprobe 整合 |
| `docs/SFTPGO_DEMO_USER.md` | SFTPgo 用戶設置 |
| `docs/API_ENDPOINTS.md` | API 端點總覽 |

View File

@@ -0,0 +1,149 @@
# 系统重启后状态报告
## 基本信息
- **报告时间**: 2026-03-27 18:36
- **系统运行时间**: 6分钟 (重启于 18:28)
- **上次关机时间**: 约 18:24
- **关机测试结果**: 部分通过 (3/8 测试通过)
## 系统健康状态
### ✅ 服务状态 (14/14 健康)
所有核心服务已自动重启并运行正常:
1. **PostgreSQL** (5432) - 正常
2. **Redis** (6379) - 正常
3. **MariaDB** (3306) - 正常
4. **n8n** (8085) - 正常
5. **Caddy** (2019) - 正常
6. **Gitea** (3000) - 正常
7. **SFTPGo** (8080) - 正常
8. **Ollama** (11434) - 正常
9. **Qdrant** (6333) - 正常
10. **MongoDB** (27017) - 正常
11. **PHP-FPM** - 运行中
12. **RustDesk** - 运行中
13. **Node.js** - 运行中
14. **Python** - 已配置
### ✅ Momentry 核心服务
- **Momentry Server** (端口 3002) - 运行中
- **Momentry Worker** - 运行中 (2个并发)
- **ASR 处理器** - 正在处理视频 (消耗大量资源)
## 系统资源
### 内存使用
- **总内存**: 16GB
- **已使用**: 15GB (94%)
- **可用**: 294MB
- **状态**: ⚠️ 内存使用率高
### CPU 负载
- **负载平均值**: 11.15, 13.17, 8.52
- **CPU 使用率**: 82.42% user, 17.57% sys
- **状态**: ⚠️ 高负载 (ASR 处理中)
### 磁盘空间
- **总容量**: 1.9TB
- **已使用**: 302GB (17%)
- **可用**: 1.5TB
- **状态**: ✅ 充足
## AI 处理器合规性
### ✅ 所有处理器 100% 合规
1. **ASR 处理器** v2.1.0 - 100% 合规
2. **OCR 处理器** v1.0.0 - 100% 合规
3. **YOLO 处理器** v1.0.0 - 100% 合规
4. **Face 处理器** v1.0.0 - 100% 合规
5. **Pose 处理器** v1.0.0 - 100% 合规
### 标准化完成度
- **已完成**: ASR, OCR, YOLO, Face, Pose
- **待完成**: ASRX, Caption, CUT, Story (低优先级)
## 文档重组状态
### ✅ v1.0 文档结构已建立
- **ARCHITECTURE/** - 17个架构文档
- **IMPLEMENTATION/** - 38个实现指南
- **REFERENCE/** - 30个参考文档
- **OPERATIONS/** - 8个运维文档
- **STANDARDS/** - 4个标准文档
- **TEMPLATES/** - 模板文件
### ✅ AGENTS.md 已更新
包含新的文档结构和配置信息
## 关机测试结果
### 测试概况
- **总测试数**: 8
- **通过**: 3 (37.5%)
- **失败**: 5 (62.5%)
- **错误**: 0
### 主要问题
1. **Redis 优雅关机失败** - 服务仍在运行
2. **PostgreSQL 优雅关机超时** - 30秒超时
3. **数据持久性测试失败** - 依赖前两个测试
### 改进建议
1. 改进服务停止脚本的超时处理
2. 添加更强大的强制停止机制
3. 优化数据库关闭顺序
## 当前运行进程
### 高资源消耗进程
1. **ASR 处理器** - 处理 `/Users/accusys/test_video/BigBuckBunny_320x180.mp4`
- 占用大量 CPU 和内存
- 预计处理完成后负载会下降
### 核心服务进程
- Momentry Server (PID: 406)
- Momentry Worker (PID: 1492)
- PostgreSQL (多个进程)
- Redis (PID: 78789)
- MongoDB (PID: 424)
- 其他服务正常
## 建议操作
### 立即操作
1. **监控 ASR 处理进度** - 当前高负载主要来自 ASR
2. **等待处理完成** - 预计完成后系统负载会恢复正常
3. **检查处理结果** - 验证 ASR 输出文件
### 短期改进
1. **优化服务停止机制** - 改进关机脚本
2. **添加资源监控** - 实时监控 CPU/内存使用
3. **完善重启测试** - 验证系统恢复能力
### 长期计划
1. **完成剩余处理器标准化** - ASRX, Caption, CUT, Story
2. **性能基准测试** - 验证 <5% 开销要求
3. **生产环境部署** - 基于标准化架构
## 总结
### 成就 ✅
1. **文档重组完成** - v1.0 结构建立
2. **AI 处理器标准化** - 5个核心处理器 100% 合规
3. **系统自动恢复** - 重启后所有服务正常
4. **配置统一完成** - ASR 配置已统一
### 待改进 ⚠️
1. **关机机制** - 需要改进服务停止逻辑
2. **资源管理** - 当前高负载需要监控
3. **测试覆盖** - 需要更多自动化测试
### 系统状态
- **整体健康度**: 良好 (服务正常,处理器合规)
- **资源状态**: 紧张 (高 CPU/内存使用)
- **稳定性**: 已验证 (通过重启测试)
---
*报告生成时间: 2026-03-27 18:37*
*系统已从关机中成功恢复*