№ 19
VPay - ví điện tử mô phỏng dùng AI để chủ động phát hiện và can thiệp giao dịch lừa đảo ngay lúc chuyển tiền (chấm điểm rủi ro + chatbot phản biện + cảnh báo người tin cậy), thay vì chỉ tuyên truyền phòng chống thụ động, nhắm tới người dùng 32–55 tuổi.
Một câu: VPay là ví điện tử chặn được tiền đi sai trước khi giao dịch hoàn tất, bằng hai cổng kiểm tra tự động và một trợ lý AI đối thoại ngay tại giây người dùng sắp bấm "Chuyển".
| Bản chạy | URL |
|---|---|
| App người dùng | https://c3-app-029.web.app |
| Admin console | https://c3-admin-029.web.app |
| Landing | https://vpay-landing.web.app |
Năm 2025, Việt Nam ghi nhận hơn 3.400 tỷ đồng thiệt hại từ 4.814 vụ lừa đảo trực tuyến. Riêng một vụ ở Bắc Giang có khoảng 200 tỷ đồng luân chuyển qua các tài khoản, gần 1.000 bị hại.
Điểm mấu chốt: phần lớn các giao dịch này do chính chủ tài khoản tự thực hiện. Nạn nhân tự đăng nhập, tự nhập PIN, tự bấm chuyển. Không có phiên đăng nhập lạ, không có thiết bị lạ, không có dấu hiệu chiếm quyền. Hệ thống làm đúng mọi bước kỹ thuật — và tiền vẫn mất, vì chủ tài khoản đang bị điều khiển từ đầu dây bên kia.
| Đang có | Chặn được | Bỏ lọt |
|---|---|---|
| OTP / sinh trắc học | người lạ dùng tài khoản | chính chủ đang bị thao túng |
| Hạn mức giao dịch | thiệt hại một lần | chia nhỏ nhiều lần là qua |
| Cảnh báo tĩnh trong app | nhắc chung chung |
| người đang hoảng loạn đọc lướt |
| Tổng đài hỗ trợ | ca khó | chỉ chạy sau khi mất tiền |
Toàn cầu, trong số nạn nhân đã báo cáo cho đơn vị thanh toán, chỉ 30% thu hồi được ít nhất một phần (GASA — Global State of Scams 2025, 42 thị trường). Nghĩa là hướng "xử lý sau khi mất tiền" đã được thử ở quy mô lớn và nó không hiệu quả.
Nội dung tuyên truyền hiện tại cũng là đọc/xem thụ động, không tạo được phản xạ đúng lúc người dùng thật sự bị dẫn dắt.
Khoảng trống thật sự nằm ở một chỗ: không có ai nói chuyện với người dùng ngay tại giây họ sắp bấm nút chuyển.
Bấm chuyển tiền → Cổng 1 → Cổng 2 → (nếu rủi ro) Trợ lý → Người dùng tự quyết
Cổng 1 — chặn cứng. Đối chiếu người nhận với danh sách đen (số tài khoản, số điện thoại, tên miền). Khớp là chặn, app chuyển thẳng sang màn BLOCK-01, không có nút vượt qua.
Cổng 2 — chấm rủi ro. Mô hình hành vi kết hợp luật tĩnh, cho ra ALLOW / REQUIRE_CONFIRMATION / BLOCK kèm reason_codes. Không dùng LLM ở cổng này — quyết định chặn tiền phải tái lập được và giải thích được.
Lớp 3 — fraud_intervention_agent. Chỉ mở khi rủi ro trung bình/cao (những mức không bị chặn cứng): agent hội thoại hỏi làm rõ, tra kịch bản lừa đảo đã số hoá, giải thích lý do nghi ngờ bằng tiếng Việt có trích nguồn. Người dùng nhập bằng bàn phím hoặc nói — bản ghi âm được chuyển thành văn bản rồi chạy đúng lượt hội thoại đó (agent trả lời bằng chữ, chưa nói lại thành tiếng).
Nguyên tắc xuyên suốt: agent không tự huỷ giao dịch. Nó tạo ma sát đúng lúc và cung cấp căn cứ — cắt đúng thứ kẻ gian cần nhất là sự vội. Quyết định cuối vẫn của người dùng.
GET /news chỉ trả bài trạng thái APPROVED.fraud_reports; số tài khoản bị cảnh báo tra lại được qua GET /accounts/{account_number}/warning-status.Bốn tầng, phụ thuộc một chiều:
| Tầng | Trách nhiệm | Ràng buộc cứng |
|---|---|---|
API (src/api/) | Validate input, xác thực, rate limit, gọi service, trả response model | Không chứa business logic, không query ORM trực tiếp |
Service (src/services/) | Toàn bộ nghiệp vụ + quản lý transaction DB | Không import fastapi, không raise HTTPException |
Agent (src/agents/) | Chấm rủi ro, hội thoại can thiệp, speech-to-text; router riêng /agents/* | Không nhúng nghiệp vụ tiền — chuyển tiền vẫn phải qua tầng service |
DB (src/db/) | ORM, engine, session | Không biết gì về tầng trên |
Ràng buộc quan trọng nhất là dòng thứ ba: tầng AI không được tự tay chuyển hay giữ tiền. Nó chỉ trả về phán quyết. Nhờ vậy một lỗi ở tầng model không bao giờ trở thành lỗi số dư.
Chỉ nhánh cảnh báo mới tốn model. Nhánh ALLOW đi thẳng, không phát sinh chi phí LLM — đây là điều khiến mô hình chi phí đứng vững ở quy mô lớn.
Mô hình trả lời "người này thường giao dịch thế nào", không trả lời "giao dịch này có lừa đảo không". Nó chỉ dựng baseline hành vi; điểm bất thường là hàm tất định tính trên baseline đó — nhờ vậy mọi phán quyết chặn tiền tái lập được và giải thích được bằng reason_codes.
Hai mô hình, train thủ công, serve bằng tra bảng:
| Model | Vai trò |
|---|---|
KMeans (k = 4, chọn k qua điểm tổng hợp silhouette + davies-bouldin + cân bằng cụm) | Phân cụm hành vi từ 5.000 hồ sơ khảo sát. Nhãn đánh số lại ổn định C0…C3 để lần train sau không hoán vị |
RandomForestClassifier | Học ánh xạ profile (tuổi, khu vực, ngành nghề, mục đích ví) → cụm. Lúc serve không gọi predict: train xong precompute 15.640 tổ hợp profile thành profile_probability_index, runtime chỉ tra bảng |
Tách hai nhóm feature vì lúc train có bộ khảo sát dài (9 trường hành vi), lúc serve chỉ có KYC + survey ngắn (4 trường profile).
Chọn baseline theo độ chắc chắn. User đã có trong user_clusters → dùng cụm đã biết; confidence ≥ 0.70 → đúng một cụm; 0.50–0.70 → trộn 2 cụm cao nhất theo trọng số; < 0.50 → GENERAL_FALLBACK. User có ≥ 10 giao dịch sạch trong 90 ngày được dựng thêm baseline cá nhân từ chính lịch sử.
Chấm điểm:
| Yếu tố | Trọng số |
|---|---|
amount — số tiền so với baseline | 0.30 |
velocity — tốc độ giao dịch | 0.30 |
recipient — người nhận mới / đã bị cảnh báo | 0.20 |
time — thời điểm bất thường | 0.10 |
balance_ratio — tỉ lệ trên số dư | 0.10 |
Thiếu số dư trước giao dịch thì bỏ balance_ratio và renormalize phần còn lại — không coi thiếu dữ liệu là rủi ro thấp. Ngưỡng HIGH 60 / MEDIUM 35, hạ còn 55 / 30 khi rơi về GENERAL_FALLBACK vì baseline chung kém chắc chắn hơn. Khớp blacklist thì trả thẳng CRITICAL / BLOCK, anomaly_score = 100, không chấm component nào.
Lớp luật apply_behavioral_policy ép REQUIRE_CONFIRMATION bất kể điểm số khi: vượt ngưỡng vét số dư (0.30 / 0.45 / 0.60 tuỳ người nhận mới / quen / thân), chia nhỏ giao dịch tới cùng người nhận trong 10 phút vượt ngưỡng động, hoặc tiếp tục chuyển tới người nhận đã bị cảnh báo.
Artifact có checksum. Model ghi atomic bằng os.replace (vpay_anomaly_model.joblib + vpay_user_clusters.csv + vpay_training_report.json kèm artifact_sha256, dataset_sha256, model_version=vpay-anomaly-v2). Lúc nạp, service verify sha256 trước khi joblib.load và từ chối artifact lệch schema_version. Giới hạn đã biết và ghi trong test: checksum bắt file hỏng hoặc bản cũ sót lại, không chặn được người có quyền ghi thư mục vì họ sửa được cả report.
fraud_intervention_agentOrchestrator tự xây, không dùng framework agent. Chia tầng:
| Thư mục | Nội dung |
|---|---|
application/ | orchestrator.py, turn_api.py (run_production_turn[_stream]), handler cho tin nhắn người dùng và sự kiện rủi ro |
domain/ | Model dữ liệu (AgentInput/Output/State, Understanding, Evidence), decide_intervention, tóm tắt bối cảnh rủi ro |
intelligence/ | Các bước gọi LLM: understand → plan → investigate → synthesize → respond |
tools/ | registry.py + phone_tool, link_tool, hotline_tool, blacklist_tool, knowledge_tool (bọc RAG), search_tool |
knowledge/ | Định tuyến tri thức + xếp hạng nguồn |
memory/ | Trạng thái case, lưu xuống agent_session_states |
guardrails/ | input_guard, output_guard |
Luồng một lượt: input_guard chặn ý đồ gây hại và câu dò danh tính → UNDERSTAND (ngoài phạm vi thì dừng) → PLAN (nếu cần hỏi lại thì dừng, hỏi người dùng) → INVESTIGATE chọn tool theo router tri thức, tra pgvector corpus 30 kịch bản lừa đảo cùng blacklist SĐT/domain/số tài khoản và trustlist → SYNTHESIZE → decide_intervention → respond, qua output_guard + redact PII trước khi trả về.
POST /agents/conversations/turn/voice nhận một request multipart, server chuyển audio thành văn bản rồi chạy tiếp đúng lượt đó, không thêm nhánh nào trong orchestrator. Không lưu audio. Transcript đi qua redact PII như tin nhắn gõ tay. Chưa có text-to-speech — xem Giai đoạn 2.fetch của expo/fetch (RN buffer toàn bộ response nên fetch mặc định làm stream mất tác dụng). Client chỉ giữ session_id; hội thoại và số lượt do server giữ trong chat_sessions, có cột version làm optimistic lock (sửa đồng thời → 409).| Lớp | Công nghệ |
|---|---|
| Backend | Python 3.13, FastAPI + Uvicorn, uv |
| Agent | Orchestrator tự xây 4 bước + tool registry; LLM qua OpenRouter gemini-3.5-flash-lite |
| RAG | pgvector trên Neon, embedding multilingual-e5-large (1024d) |
| Speech-to-text | qwen-audio-3.0-asr-flash (DashScope) |
| Risk engine | scikit-learn + pandas/numpy + luật hành vi — không LLM |
| Dữ liệu | Neon Postgres + pgvector, SQLAlchemy 2.0 + Alembic; SQLite in-memory cho test |
| Rate limit | Redis — cửa sổ cố định + cửa sổ trượt + slot đồng thời, fail-open |
| Mobile | Expo SDK 57, React Native + TypeScript. State bằng Context + reducer, không Redux/Zustand |
| Web | React + Vite — admin console + landing (4 entry MPA), khác origin |
| Quan trắc | Trace tự xây (traces / trace_spans / trace_feedback) + LangSmith tuỳ chọn |
| DevOps | Docker, GitHub Actions CI/CD → Cloud Run + Firebase Hosting, Terraform |
| Kiểm thử | pytest + httpx + bộ eval chạy live ở evals/ |
| Cơ chế | Áp dụng | Ngưỡng |
|---|---|---|
| Cửa sổ cố định theo IP | POST /auth/login | 10 / phút |
| Cửa sổ cố định theo user | /transactions/* | 30 / phút |
| Cửa sổ cố định theo user | /agents/transactions/evaluate | 60 / phút |
| Cửa sổ trượt theo user | 3 endpoint chat | 20 / phút |
| Slot xử lý đồng thời | 3 endpoint chat | capacity 50, quá 60 giây thì slot tự thu hồi |
Vượt ngưỡng trả 429, cạn pool DB trả 503 pool-timeout — cả hai kèm Retry-After.
AUTOCOMMIT pool riêng cho đường chỉ SELECT — blacklist lookup và RAG retrieval. Đường đọc nóng không tranh pool với đường ghi tiền.Trace cho path /api/v1/agents; span con lồng vào qua contextvar nên route handler không phải biết gì về tracing. Lượt rơi vào nhánh dự phòng được đánh dấu traces.degraded = True — vì HTTP vẫn 2xx nên nếu chỉ nhìn status_code thì success rate sẽ báo 100% sai lệch.JWT access HS256 sống ngắn + refresh token opaque lưu hash trong DB, xoay vòng mỗi lần /auth/refresh. Claim typ tách hẳn tài khoản user (app) và staff (console); staff đăng nhập thêm được bằng Google qua Firebase Admin SDK. Định danh người dùng là SĐT VN nội địa + PIN, chuẩn hoá tập trung một chỗ.
Quy ước tiền: số nguyên VND, không dùng float; mọi thay đổi balance phải sinh một row transactions trong cùng DB transaction. Người nhận lưu dưới dạng HMAC ổn định (counterparty_account_hash), không lưu thô. Redact PII hai chiều ở tầng agent, phân quyền theo vai trò, nhật ký thao tác quản trị (admin_audit_log).
Hạ tầng khai báo bằng Terraform: Cloud Run service + job migrate, Artifact Registry, Redis chạy trên máy ảo GCE trong VPC riêng (không phải Memorystore), Secret Manager, Workload Identity Federation, Firebase. Terraform không provision database — Neon nằm ngoài GCP, chỉ có connection string trong Secret Manager.
CI chạy song song backend (ruff + pytest), admin, landing, mobile-web; CD chỉ chạy khi CI xanh, xác thực bằng OIDC/WIF nên không giữ khoá service account. Backend không chạy alembic upgrade head lúc khởi động — nhiều instance Cloud Run khởi động song song sẽ chạy trùng; migration là Cloud Run Job riêng.
Không phải bản vẽ. Đang chạy thật trên 3 site Firebase Hosting + backend Cloud Run: hai cổng kiểm tra, agent hội thoại (nhập bằng chữ hoặc bằng giọng nói), trace, admin console phân quyền, app di động, landing page, service crawler tin cảnh báo, CI/CD.
Toàn bộ eval chạy gọi thật OpenRouter, thật Neon/pgvector, đúng đường chạy mà API dùng:
| Bộ eval | Quy mô | Kết quả chính |
|---|---|---|
| RAG retrieval | 90 case | Recall@1 84,4% · Recall@5 100% · MRR 0,916 · p95 2,29s |
| Robustness / adversarial | 120 lượt, 10 nhóm | 0 lộ danh tính/prompt · 0 crash · 0 lượt lặp dữ liệu nhạy cảm |
Kiểm tra chủ động (proactive_check) | 39 case | Trích đúng thực thể 38/39 (97,4%) |
| Risk engine detection | 105 case gán nhãn | F1 0,882 · Precision 85,7% · Recall 90,9% · FPR 25,6% |
| Contract regression | 8 case | PASS — safety 100%, grounding 100%, p95 0,084s |
FPR 25,6% là con số phải nói thẳng: cứ 4 cảnh báo thì có 1 báo động sai. Nó chấp nhận được chỉ vì mức đó không chặn giao dịch — nó mở hội thoại. Chặn cứng chỉ xảy ra khi khớp danh sách đen.
Xu hướng siết định danh chính chủ và tăng trách nhiệm của đơn vị thanh toán trong phòng chống lừa đảo đang đi cùng chiều với sản phẩm. Console có sẵn KPI, nhật ký thao tác và khả năng đọc lại từng lượt AI — đây chính là thứ hồ sơ tuân thủ đòi hỏi.
Vận hành thật cần thêm một vai trò chuyên trách dữ liệu cảnh báo: tiếp nhận nguồn, đối soát, xử lý khiếu nại chặn nhầm. Các vai trò còn lại (SRE, developer, security, điều hành) đã có chỗ đứng sẵn trong console.
Chỉ nhánh cảnh báo tốn model, chạy trên model hạng flash-lite. Nhánh ALLOW không gọi LLM. So sánh đúng không phải với "chi phí một API call" mà với chi phí một cuộc gọi tổng đài xử lý ca mất tiền — chênh nhau nhiều bậc.
| Rủi ro | Cách xử lý hiện tại |
|---|---|
| Chặn nhầm người dùng thật | Hai cổng tách rời: chặn cứng chỉ khi khớp danh sách đen; phần còn lại chỉ tạo ma sát, không khoá giao dịch |
| AI nói sai / khẳng định thừa | output_guard chặn khẳng định tuyệt đối; nguyên tắc "chưa ghi nhận ≠ an toàn"; adversarial eval 120 lượt không lộ prompt |
| AI quyết định thay người dùng | Agent không có quyền huỷ giao dịch; quyết định chặn do risk engine không-LLM đưa ra, có reason_codes tái lập được |
| Model artifact bị tráo | Verify sha256 trước khi load, từ chối artifact lệch schema_version — nhưng không chống được người có quyền ghi thư mục |
| Lỗi hạ tầng phụ | Redis fail-open; hai engine DB tách pool; landing tĩnh không gọi API ví |
| Còn hở đã biết | Định danh mới ở mức mô phỏng, CORS đang mở ở môi trường dev, quy trình khiếu nại chặn nhầm chưa hoàn chỉnh, corpus kịch bản cập nhật thủ công |
Tỉ lệ tự huỷ giao dịch sau hội thoại, tỉ lệ báo động sai, tỉ lệ rời bỏ tại màn cảnh báo, độ trễ p50/p95, chi phí mỗi lượt.
Hai cổng kiểm tra + fraud_intervention_agent + kiểm tra chủ động + người tin cậy + bản tin cảnh báo + trace + admin console phân quyền + app di động.
Hội thoại bằng giọng nói hai chiều. Hiện giọng nói mới đi một chiều: người dùng nói, hệ thống chuyển thành văn bản, agent trả lời bằng chữ. Bước tiếp theo là đối thoại thoại-với-thoại thật sự — bổ sung text-to-speech tiếng Việt, streaming song công, phát hiện ngắt lời (barge-in), tách lượt nói bằng VAD, kéo độ trễ mỗi lượt xuống ngưỡng nghe được là hội thoại chứ không phải chờ máy xử lý. Đây không phải tính năng trang trí: người đang bị dẫn dắt qua điện thoại thường đang cầm máy áp tai, không rảnh mắt để đọc — muốn cắt ngang được kịch bản của kẻ gian thì trợ lý phải nói vào đúng khoảnh khắc đó. Ràng buộc giữ nguyên: không lưu audio, redact PII trước khi rời máy.
Cùng giai đoạn: quy trình khiếu nại khi bị chặn nhầm, hạ FPR của risk engine bằng dữ liệu vận hành thật, dọn thư mục chết còn sót từ scaffold.
Tối đa hoá data pipeline cho cả hai luồng đang phụ thuộc dữ liệu ngoài — luồng kiểm tra của trợ lý (RAG kịch bản, danh sách đen) và luồng bản tin. Hiện crawler mới chạy trên một tập nguồn RSS/HTML hằng ngày; đích đến là pipeline đầy đủ năm chặng:
| Chặng | Nội dung |
|---|---|
| Thu thập theo lịch | Mở rộng nguồn ra ngoài tin tức: mạng xã hội, nhóm cộng đồng cảnh báo, kênh chính thức của cơ quan chức năng và ngân hàng. Lập lịch riêng theo tốc độ thay đổi của từng nguồn |
| Data lake | Đổ dữ liệu thô nguyên trạng, giữ nguyên bản gốc và metadata nguồn/thời điểm — tách hẳn khỏi dữ liệu đã xử lý, để chạy lại pipeline trên dữ liệu cũ khi đổi cách xử lý |
| Validate — AI + human in the loop | Lớp máy lọc trước: khử trùng lặp, loại tin rác, chấm độ tin cậy nguồn, gắn cờ ca mơ hồ. Lớp người duyệt sau, chỉ nhìn phần máy không chắc. Không có tin nào vào kho tri thức mà không qua một trong hai cửa |
| Transform | Chuẩn hoá về một schema chung: loại kịch bản, dấu hiệu nhận biết, thực thể liên quan (số điện thoại, tài khoản, tên miền), mức độ, nguồn trích dẫn. Chính schema này là thứ agent trích ra khi trả lời |
| Embed → vector DB | Cắt chunk cha/con, sinh embedding, đẩy vào pgvector cùng chỗ với corpus 30 kịch bản hiện có. Kịch bản mới xuất hiện ngoài đời được trợ lý dùng trong ngày, không phải chờ đợt cập nhật thủ công |
Mấu chốt: hiện tại corpus kịch bản là tài sản tĩnh — soạn một lần, dùng mãi. Kịch bản lừa đảo thì đổi hằng tuần. Pipeline này biến corpus thành tài sản sống, và đây chính là chỗ khoảng cách với đối thủ nới rộng theo thời gian.
Cùng giai đoạn: nối dữ liệu cảnh báo chính thức từ cơ quan chức năng và ngân hàng, học từ nhãn thực tế — mỗi ca chặn đúng/chặn nhầm quay lại làm dữ liệu huấn luyện, khép kín vòng lặp dữ liệu. Kèm theo đó là tự động hoá vòng train risk engine (hiện train chạy thủ công, không có job tự động).
Game mô phỏng lừa đảo bằng video tương tác ngay trong ví: người chơi chọn nhánh xử lý, hệ thống giải thích dấu hiệu nhận biết sau mỗi quyết định. Kết quả chơi trở thành tín hiệu đầu vào cho hồ sơ rủi ro — người từng sập bẫy trong mô phỏng sẽ được cảnh báo sớm hơn ở đời thật. Hiện hiển thị "Sắp ra mắt" trên app.
Với ví điện tử triển khai VPay: chi phí sự cố giảm vì chặn ngay từ đầu vào, tổng đài nhẹ đi vì agent xử lý lớp một, tuân thủ dễ hơn vì mọi lượt AI đọc lại được.
Đối thủ sao chép được kiến trúc trong vài tháng. Cái họ không sao chép được là dữ liệu cảnh báo đã tích luỹ và vòng lặp học từ nhãn thực tế — "ví an toàn nhất" là định vị chỉ có được sau thời gian, không mua được bằng tiền.
Nguồn số liệu bài toán: Báo Công an nhân dân 2025, Tuổi Trẻ 25/4/2025 (đã ẩn danh nạn nhân), GASA — Global State of Scams 2025. Số liệu kỹ thuật và eval: P-029/README.md, P-029/ARCHITECTURE.md, P-029/docs/eval-evidences.md.