№ 27
ORI Cabin Copilot là trợ lý cabin tiếng Việt chạy hoàn toàn cục bộ trên xe (đích triển khai AIBOX 6490), giải quyết ba vấn đề của trợ lý giọng nói ô tô ở Việt Nam: phụ thuộc mạng, nhận dạng tiếng Việt kém, và rủi ro an toàn khi mô hình tự do sinh lệnh. Hệ thống là một pipeline giọng nói → STT (Gipformer-65M-RNNT) → SLM (Qwen3.5-4B) → kiểm duyệt → thực thi, phục vụ bốn nhóm lệnh: điều hòa, đèn trần, âm nhạc và tìm đường. Điểm cốt lõi là SLM bị coi là thành phần không đáng tin: mọi đề xuất phải qua tầng policy kiểm JSON schema, ép về đúng catalog 8 tool đóng và kẹp tham số trong khoảng, nên không bao giờ chạm được vào trạng thái xe bằng lệnh ngoài danh sách. Ba nhóm cabin hoạt động offline, riêng tìm đường là ngoại lệ có chủ đích gọi dịch vụ bản đồ bên ngoài. Phần lõi đã chạy end-to-end ở mức mô phỏng, kèm TTS tiếng Việt và hạ tầng trace/eval để kiểm chứng.
№ 27
Demo Day Zine
Trợ lý giọng nói trên ô tô ở Việt Nam gặp ba vấn đề cùng lúc:
Sản phẩm nhắm tới bốn nhóm lệnh: điều hòa, đèn trần, âm nhạc, và tìm đường (chỉ tới mức phân giải điểm đến, chưa tính tuyến rẽ từng bước). Điều khiển ghế đã bỏ hẳn khỏi phạm vi.
Đích triển khai là thiết bị biên AI BOX 6490, chạy offline trên xe cho ba nhóm cabin. Nhóm tìm đường là ngoại lệ có chủ đích: cần dữ liệu bản đồ nên gọi dịch vụ bên ngoài.
docs/team_task_assignment.md.Pipeline lệnh chạy tuần tự (Python service, không dùng LangGraph trên đường thực
thi tool — docs/architecture/codebase-blueprint.md §2). Các stage thực tế đang
chạy, đặt tên đúng như trong trace (docs/architecture/mvp-pipeline-tong-the.md,
mvp-tts.md):
audio upload
→ audio.validate → audio.normalize (ffprobe/ffmpeg → WAV PCM16 mono 16 kHz)
→ vad.whole_buffer (đệm nguyên câu, chưa phải VAD thật)
→ stt.gipformer (transcript tiếng Việt)
→ nlu.normalize
→ slm.plan (Qwen3.5-4B) (đề xuất tool call dạng JSON có ràng buộc)
→ policy (schema + allowlist + range) (deterministic)
→ executor (proposal_only | geoapify)
→ render_response → UI text
→ render_speech → tts.stream → loa (nhánh riêng, không chặn lệnh cabin)
apps/web/): React 19 + Vite + TypeScript + Tailwind v4. Trang
cockpit () là luồng chính; có khu kỹ sư ()
xem số liệu request/log/trace thời gian thực.Simulator.tsxEngineerConsole.tsxapps/api/src/vivi/): Python 3.12, FastAPI, Pydantic v2. Bộ điều
phối là application/research_pipeline.py (ResearchPipeline), phơi API dưới
/api/v1. Kiến trúc phân tầng nghiêm ngặt (codebase-blueprint.md §4):
api/ (HTTP) · application/ (thứ tự use case) · domain/ (contract) ·
nlu/slm/ · policy/ (kiểm duyệt) · tools/ (executor) · voice/
(STT/TTS) · observability/ (trace/latency). Mỗi tầng có ràng buộc "không
được làm" — ví dụ voice/ không quyết định intent, policy/ không sửa state.llama-server (SLM). "Server" ở đây chỉ là ranh giới process trên
cùng một máy (Qwen ở cổng 8080, FastAPI ở 8000), không phải cloud.Sơ đồ chi tiết: docs/architecture_diagram.md, ARCHITECTURE.md.
Ba tầng tài liệu — đọc đúng phạm vi để tránh nhầm "đã có" với "mục tiêu":
| Tầng | Nguồn | Nội dung |
|---|---|---|
| Kiến trúc mục tiêu (Gate G1) | ARCHITECTURE.md, codebase-blueprint.md | 4 domain gồm cả navigation, seat/media, SQLite, confirmation, rule-first NLU, RAG. Nhiều module là reserved scaffold ghi not implemented. |
| Research slice (~2026-08-13) | mvp-*.md | 6 tool cabin, ProposalOnlyExecutor, JSON trace. Đây là mốc tài liệu mô tả. |
Code nhánh main hiện tại | Xác minh trực tiếp | Đã vượt research slice: 8 tool (thêm search_place/get_directions), executor Geoapify thật, TTS đã có. |
Sự tồn tại của một file không đồng nghĩa capability đã hoàn thành — blueprint
giữ scaffold persistence/, rag/, seat tools ở dạng chưa import vào runtime.
STT — Gipformer-65M-RNNT (docs/architecture/mvp-stt-gipformer.md):
modified_beam_search, 16 kHz, feature_dim=80;
có tùy chọn INT8 để benchmark footprint (không mặc định coi INT8 = FP32 về chất lượng).AudioPreprocessor kiểm nội dung bằng ffprobe (không tin MIME/đuôi file),
rồi ffmpeg tạo WAV PCM signed-16 mono 16 kHz. Contract: WAV/MP3/M4A/FLAC/OGG,
≤25 MB, ≤60 s; lỗi trả HTTP 413 (quá dung lượng) / 422 (format/duration/no-speech)
/ 503 (runtime chưa sẵn sàng). Transcript rỗng → no_speech, không gọi Qwen.GipformerSTT khóa concurrency ở mức một, chạy blocking inference trong worker
thread. Kết quả kèm RTF (real-time factor) để đo tốc độ tương đối thời lượng.SLM — Qwen3.5-4B Q4_K_M GGUF qua llama.cpp llama-server ở 127.0.0.1:8080
(docs/architecture/mvp-slm-agent.md; số xác minh trong pipeline.local.toml):
temperature=0.0, seed=3407, non-thinking, max_tokens=256,
context_size=8192 (lưu ý: doc mvp-slm-agent.md còn ghi 4096 — đã lỗi
thời so với config; con số đúng theo code là 8192).PlannerEnvelope
(structured_output=json_schema của llama-server), nên output luôn parse được.apps/api/resources/prompts/slm_system_vi.txt) + transcript hiện tại + JSON
Schema của decision và tool. Không gửi audio thô, trace cũ, hay kết quả turn
trước — mỗi turn độc lập.TTS — Piper VITS vi_VN-vais1000-medium qua sherpa-onnx
(docs/architecture/mvp-tts.md) — nằm ngoài đường thực thi lệnh; mất tiếng
không làm hỏng lệnh cabin:
render_response() (UI) và render_speech() (loa) sinh từ cùng template,
chỉ khác lớp đại lượng: 24°C ↔ "hai mươi tư độ", 30% ↔ "ba mươi phần trăm"
— bắt buộc vì espeak-ng không đọc đúng số/%/°C theo tiếng Việt.sha256(text|model|speaker|speed) → WAV) thay vì thu âm sẵn;
vivi tts-warm render trước 101 câu hay gặp.Ghi chú: VAD hiện là whole_buffer (đệm nguyên câu), chưa phải VAD thật —
chưa có endpointing streaming. Wake word "ORI" và tự ngắt khi im lặng chạy phía
trình duyệt.
Đây là điểm cốt lõi về an toàn: SLM bị coi là thành phần không đáng tin. Mọi
đề xuất của nó phải qua tầng policy/ trước khi chạm vào trạng thái xe.
domain/tool_names.py):
set_climate_power, control_climate_temperature, set_ceiling_light_power,
control_ceiling_light_brightness, set_music_playback, control_music_volume,
search_place, get_directions.policy/proposal_validator.py): parse JSON thô → kiểm
Pydantic strict → ép về đúng tool trong allowlist (policy/tool_allowlist.py),
kẹp tham số trong khoảng (nhiệt độ 16–30 °C, độ sáng/âm lượng 0–100 %), tối đa
4 lệnh mỗi lượt.validation_retries=1 — nếu validate hỏng, đính kèm
chi tiết lỗi vào prompt và cho model thử lại một lần, không có lần hai; vẫn sai
hoặc server lỗi → decision=error, zero tool call.domain/tool_calls.py: Decision): PROPOSED,
CLARIFICATION, UNSUPPORTED, GREETING, ERROR. Tool ngoài allowlist,
field thừa, range sai, hoặc non-proposed mà kèm tool call đều bị từ chối.Ngữ nghĩa operation của 3 tool control_* (mvp-slm-agent.md §3.1) — ý
nghĩa của value do operation quyết định, không suy từ độ lớn con số:
| operation | value là | Range |
|---|---|---|
set | giá trị đích tuyệt đối | nhiệt độ 16–30 °C · độ sáng/âm lượng 0–100 % |
increase/decrease | bước thay đổi tương đối | nhiệt độ 0,5–5 °C · độ sáng 1–50 % · âm lượng 1–30 % |
value=null ở nhánh tương đối để validator điền mặc định (1 °C · 10 % · 5 %).
Chống prompt injection cùng ranh giới (mvp-slm-agent.md §4): transcript chỉ
là dữ liệu người dùng — nó không thể mở rộng allowlist hay đổi schema. Đây không
phải sandbox bảo mật hoàn chỉnh, nhưng deterministic validation đảm bảo zero
tool ngoài catalog. Các lệnh ngoài phạm vi (phanh, ga, vô lăng, cửa/khóa, ADAS)
phải trả unsupported, không được map sang tool gần nhất.
Nguyên tắc bất biến: model chỉ sinh đề xuất, không phải hành động; chỉ tầng thực thi xác định (deterministic) mới đổi được trạng thái cabin. Với tìm đường, model không bao giờ sinh toạ độ — nó chỉ trích tên/place_id, còn toạ độ và origin do executor tự điền.
executed=false, chưa đổi trạng thái phần cứng thật — đúng với giai
đoạn nghiên cứu/mô phỏng.tools/geoapify_executor.py) là executor thật đầu
tiên của hệ thống. Config pipeline.local.toml đặt
executor_backend = "geoapify". Nó gọi 3 endpoint Geoapify: autocomplete
(tìm), place-details (lấy lat/lon), routing (tính lộ trình distance_m/duration_s).search_place và
get_directions, executor phân giải điểm đến trước; nếu đúng một ứng viên thì
tự nối sang tính đường; nếu nhiều ứng viên thì trả NEEDS_DISAMBIGUATION để
hỏi lại tài xế.confident_pick) — chi tiết ở
docs/research-category-va-khu-trung-lap-ket-qua-dia-diem.md.Lưu ý trạng thái: phần backend tìm đường đã sẵn sàng. Ngoài ra
history_turns = 0— hội thoại nhiều lượt được ghi nhưng chưa ghép vào prompt (mỗi lượt xử lý độc lập). Đây là các khoảng cách cần đóng, không phải lỗi thiết kế.
data/artifacts/traces/YYYY-MM-DD/),
có lifecycle running/completed và tồn tại qua restart. Mỗi attempt lưu: input,
config/model path, prompt hash, response schema, raw output, validation error,
HTTP metadata, latency, decision, tool proposal và executed=false.mvp-pipeline-tong-the.md §4): span
browser (recording.capture, transport.upload, response.ui) đánh
source=client; span API đánh source=server. Lưu ý bẫy cộng trùng:
transport.upload là round-trip nên đã bao gồm thời gian xử lý server —
UI hiển thị tách riêng, không cộng vào tổng server pipeline.eval/: run_command_eval (độ chính xác lệnh),
run_stt_eval (WER/CER + latency p50/p95 + RTF), run_tts_eval (round-trip
WER/CER, TTFA, RTF), dataset eval/datasets/commands_vi.smoke.jsonl,
tts_manifest.vi.jsonl.tests/ (unit, contract, integration): normalizer,
cabin state, clarify loop, executor metrics, và nhiều test Geoapify
(test_geoapify_dedup.py, test_geoapify_confident_pick.py, v.v.).Ở khâu thiết kế, nhóm cân nhắc ba kiến trúc pipeline. Kiến trúc A là kiến trúc đang chọn và đang chạy; B và C là hai hướng nghiên cứu để cải thiện độ chính xác với tên riêng/địa danh — chưa phải giải pháp đã chắc chắn, cần thực nghiệm mới kết luận.
Kiến trúc A — hiện tại (đã chọn). Một luồng STT duy nhất; nếu kết quả mơ hồ thì hỏi lại tài xế (human-in-loop), phản hồi của SLM đọc ra bằng TTS.
flowchart LR
A[Audio] --> VAD --> STT --> SLM --> TOOL[Tool]
TOOL --> TTS[TTS: phản hồi SLM]
SLM -. kết quả mơ hồ .-> HITL[Hỏi lại tài xế]
HITL -. TTS câu hỏi .-> A
Kiến trúc B — hai luồng STT nhẹ/nặng, cổng theo độ tự tin của SLM (nghiên cứu). Audio được đệm vào buffer trước, rồi mới đưa ra model STT. Chạy STT nhẹ trước; nếu độ tự tin của SLM thấp thì lấy lại audio từ buffer cho STT nặng chạy lại. Vẫn có human-in-loop ở cuối khi kết quả mơ hồ.
flowchart LR
A[Audio] --> VAD --> BUF[Buffer âm thanh]
BUF --> STTL[STT nhẹ] --> SLM1[SLM]
SLM1 --> GATE{Độ tự tin SLM}
GATE -->|đủ cao| TOOL[Tool]
GATE -->|thấp| STTH[STT nặng]
BUF --> STTH
STTH --> SLM2[SLM] --> TOOL
TOOL --> TTS[TTS]
SLM1 -. mơ hồ .-> HITL[Hỏi lại tài xế]
SLM2 -. mơ hồ .-> HITL
Đánh đổi (chưa kiểm chứng): chạy hai model STT tốn thêm bộ nhớ/độ trễ khi rơi vào nhánh nặng; ngưỡng cổng phải hiệu chỉnh để không kích hoạt oan.
Kiến trúc C — hai luồng STT (nhẹ + NER) song song (nghiên cứu). VAD xong, audio được đưa đồng thời vào STT nhẹ và một STT chuyên nhận dạng tên riêng/ địa danh (NER). "Trộn kết quả" nghĩa là thay cụm tên riêng do STT-NER phát hiện vào transcript của STT nhẹ, rồi mới đưa cho SLM. Vẫn có human-in-loop ở cuối.
flowchart LR
A[Audio] --> VAD --> BUF[Buffer âm thanh]
BUF --> STTL[STT nhẹ]
BUF --> STTN[STT-NER: tên riêng/địa danh]
STTL --> MERGE[Trộn: thay cụm tên riêng]
STTN --> MERGE
MERGE --> SLM --> TOOL[Tool] --> TTS[TTS]
SLM -. mơ hồ .-> HITL[Hỏi lại tài xế]
Đánh đổi (chưa kiểm chứng): cần một STT-NER đủ tốt cho tiếng Việt và luật trộn đáng tin; nếu ranh giới cụm tên riêng sai thì trộn hỏng transcript. B và C cùng nhắm blocker tên địa danh tiếng Anh do người Việt nói (xem §3.3) nhưng bằng hai cơ chế khác nhau — sẽ so bằng thực nghiệm trước khi chọn.
history_turns = 0 khiến đa lượt chưa thực sự dùng lịch sử.**Độ chính xác lệnh và WER của STT: chưa có số chính thức. Cần chạy và đo latency trên máy đích.
Ba metric chấm luồng gọi tool đã chốt (plan/ke-hoach-3-metric-goi-tool.md),
định nghĩa lấy nguyên văn:
search_place rồi get_directions).Láng/Lãng).Hai cảnh báo đọc số bắt buộc kèm: (a) báo cả hai mẫu số @all (mọi lượt, đo cả
năng lực biết khi nào không gọi tool) và @call (chỉ lượt phải gọi); (b) 22,2%
slot là hằng số (ví dụ destination_place_id="pending", vehicle="car") đẩy Slot
F1 lên "điểm không công" — nên phải in kèm Slot-F1@informative (bỏ 2 slot hằng).
Riêng TTS đã có số đo — nhưng đo trên máy dev x86, 2 thread, 22 câu, và chưa validate trên ARM64/AIBOX:
| Chỉ số | Cache tắt | Cache bật |
|---|---|---|
| TTFA p50 | 305 ms | 17 ms |
| TTFA p95 | 378 ms | 108 ms |
| RTF p50 | 0,157 | 0,008 |
| Round-trip WER / CER | 2,19 % / 0,77 % | — |
Hai caveat bắt buộc kèm số này: (a) mục tiêu TTFA < 150 ms chỉ đạt khi cache trúng — câu đơn không cache có TTFA ≈ thời gian tổng hợp cả câu (~300 ms); (b) round-trip WER đo độ dễ nghe tương đối với Gipformer, không phải MOS — một model có thể điểm cao mà vẫn nghe như robot, phải nghe tay trước khi chốt. 5/22 case lệch là do lỗi STT của Gipformer, không phải TTS.
Một cạm bẫy đo lường đã biết cho STT: WER công bố của Gipformer được chuẩn hóa bằng cách bỏ dấu thanh, nên không so trực tiếp với các mô hình báo cáo WER có dấu — benchmark phải báo cáo cả hai chế độ.
==.history_turns > 0) và kiểm chứng bộ nhớ hội thoại.POST /api/v1/manual/query trả
no_evidence.Mục tiêu tuần tới là thu âm để biến bộ transcript chuẩn (đã có) thành golden dataset chấm được đầu-cuối bằng ba metric ở §3.4.
@all, @call, Slot-F1@informative như §3.4) và WER STT ở
cả chế độ có dấu / bỏ dấu thanh.Bước đưa hệ thống lên AI BOX 6490 nhằm đo trên phần cứng biên thật những chỉ số mà hạ tầng dev x86 / GPU đám mây (ví dụ T4) không mô phỏng được. Đây là phạm vi benchmark/mô phỏng nghiên cứu, không phải chuẩn xe hơi (automotive-grade) — thiết bị không sinh ra cho miền ô tô.
Thông số kỹ thuật AI BOX 6490 (xem Nguồn):
Vì sao phải đo trên thiết bị (số liệu không mô phỏng được ở nơi khác):
Trạng thái & phần việc còn lại: các bước SSH + kiểm cấu hình đã làm xong. Phần port còn lại:
llama.cpp + sherpa-onnx (baseline CPU-only), đo
latency/RAM/nhiệt/tải bền.| Nội dung | Đường dẫn |
|---|---|
| Kiến trúc mục tiêu (Gate G1) & ranh giới tin cậy | ARCHITECTURE.md, docs/architecture_diagram.md |
| Blueprint codebase (cây thư mục, trách nhiệm tầng) | docs/architecture/codebase-blueprint.md |
| Pipeline đang chạy thật (tên stage, latency) | docs/architecture/mvp-pipeline-tong-the.md |
| Chi tiết STT Gipformer | docs/architecture/mvp-stt-gipformer.md |
| Chi tiết SLM Qwen + ngữ nghĩa tool | docs/architecture/mvp-slm-agent.md |
| Chi tiết TTS + số đo + so chọn model | docs/architecture/mvp-tts.md |
| Bộ điều phối pipeline | apps/api/src/vivi/application/research_pipeline.py |
| Catalog tool đóng | apps/api/src/vivi/domain/tool_names.py |
| Kiểm duyệt & allowlist | apps/api/src/vivi/policy/proposal_validator.py, tool_allowlist.py |
| Executor tìm đường | apps/api/src/vivi/tools/geoapify_executor.py |
| Khử trùng lặp địa điểm | docs/research-category-va-khu-trung-lap-ket-qua-dia-diem.md |
| Cấu hình runtime | apps/api/configs/pipeline.local.toml |
| Biểu mẫu đánh giá (chưa điền) | eval/results/report.md |
| Phân công nhóm & cột mốc | docs/team_task_assignment.md |