Một trợ lý phỏng vấn hoạt động mà không cần gọi điện toán đám mây. Không dùng API bên ngoài. Không cần tải lên sơ yếu lý lịch. Không rò rỉ âm thanh. Chỉ là máy tính xách tay của tôi thôi. Vì vậy, tôi đã xây dựng một trợ lý AI cục bộ có chức năng: – Ghi âm từ trình duyệt – Chuyển đổi giọng nói thành văn bản – Đọc mô tả công việc – Lưu trữ bộ nhớ hội thoại bằng cách sử dụng các embedding. – Sàng lọc ứng viên dựa trên câu trả lời thực tế – Giải thích lý do tại sao ứng viên phù hợp hoặc không phù hợp. 
Nếu bạn muốn 100% local – không cloud – không API ngoài – không rò rỉ dữ liệu, thì bạn phải thiết kế workflow theo tư duy:
Browser → Local service → n8n → Local LLM → Local Vector DB → trả kết quả
Không có một byte nào ra internet.
Nếu dùng React, .NET và trí tuệ nhân tạo cục bộ thì hệ thống sàng lọc phỏng vấn địa phương gồm ba phần:
- Ứng dụng React dành cho cuộc trò chuyện âm thanh
- API .NET dành cho điều phối
- Ngăn xếp AI cục bộ cho khả năng suy luận và ghi nhớ
Trợ lý đặt câu hỏi, lắng nghe, ghi nhớ các câu trả lời trước đó và so sánh chúng với bản mô tả công việc.
Ngăn xếp công nghệ
Phản ứng (Vite)
API Web .NET 8
Ollama (thạc sĩ luật địa phương)
Hạt nhân ngữ nghĩa
bộ nhớ vectơ cục bộ
API âm thanh trình duyệt
Mọi thứ đều vận hành trên máy của bạn.
Trong bài này sẽ trình bày workflow n8n theo từng lớp rõ ràng để bạn có thể build ngay.
KIẾN TRÚC TỔNG THỂ (LOCAL ONLY)
Thành phần đề xuất:
- n8n (self-host, local)
- Ollama (chạy LLM local)
- whisper.cpp hoặc faster-whisper (STT local)
- Qdrant / Chroma (vector DB local)
- Browser MediaRecorder API (ghi âm local)
- SQLite / Postgres local (lưu metadata nếu cần)
PHẦN 1 — GHI ÂM TỪ TRÌNH DUYỆT
Không dùng cloud STT.
Cách làm
Trình duyệt dùng:
- MediaRecorder API
- Xuất file
.webmhoặc.wav - POST file đó về n8n qua Webhook node
n8n Workflow 1 — Nhận Audio
Webhook Node
- Method: POST
- Binary data: ON
- Expect field:
audio
Write Binary File Node
- Lưu file vào:
/data/interview/audio/{{ $now }}.wav
PHẦN 2 — SPEECH TO TEXT (LOCAL)
Dùng whisper.cpp hoặc faster-whisper
Ví dụ command:
./main -m models/ggml-large-v3.bin -f audio.wav -otxt
n8n Node
Execute Command Node
Command:
whisper audio.wav --model large-v3 --output_dir /data/interview/transcript
Output: file .txt
Read Binary File Node
- Đọc transcript.txt
PHẦN 3 — ĐỌC JOB DESCRIPTION
JD có thể:
- Lưu sẵn trong DB
- Hoặc upload một lần rồi embedding
Workflow: Chuẩn hóa JD
Set Node
Tạo field:
job_description
Split In Batches Node
Chunk 500–800 tokens
Embedding Node (local Ollama)
Model embedding local ví dụ:
nomic-embed-text
HTTP Request Node:
POST:
//localhost:11434/api/embeddings
Body:
{
"model": "nomic-embed-text",
"prompt": "{{ $json.chunk }}"
}
Insert vào Qdrant (local)
HTTP:
//localhost:6333/collections/jd/points
PHẦN 4 — LƯU BỘ NHỚ HỘI THOẠI
Mỗi câu trả lời ứng viên:
- Lưu raw text
- Tạo embedding
- Insert vào collection
conversation
Workflow:
Transcript → Embedding → Qdrant → Done
PHẦN 5 — SÀNG LỌC ỨNG VIÊN
Bây giờ là phần quan trọng nhất.
Logic
- Lấy transcript
- Tìm các đoạn JD liên quan (semantic search)
- So sánh câu trả lời với yêu cầu
- Tính điểm
- Giải thích lý do
Workflow 2 — Phân tích ứng viên
Webhook Trigger (sau mỗi câu hoặc cuối buổi)
Embed transcript
POST Ollama embedding
Search Qdrant
Search top 5 đoạn JD liên quan:
POST /collections/jd/points/search
Merge Node
Gộp:
- Transcript
- Relevant JD chunks
LLM Node (Ollama local)
POST:
//localhost:11434/api/generate
Body:
{
"model": "mistral",
"prompt": "
Bạn là chuyên gia tuyển dụng.
So sánh câu trả lời ứng viên với yêu cầu công việc.
JD:
{{ $json.jd }}
Câu trả lời:
{{ $json.answer }}
Trả về:
- Điểm (0–10)
- Lý do phù hợp
- Lý do không phù hợp
- Kỹ năng thiếu
"
}
PHẦN 6 — LƯU KẾT QUẢ
Bạn có thể:
- Lưu JSON file local
- Hoặc SQLite
Node:
Write Binary File hoặc Execute Query
SƠ ĐỒ LUỒNG HOẠT ĐỘNG
Browser
→ Webhook
→ Save audio
→ Whisper
→ Transcript
→ Embedding
→ Qdrant
→ Search JD
→ LLM Evaluate
→ JSON Report
TỐI ƯU CHUYÊN SÂU
Nếu bạn muốn hệ thống chuyên nghiệp hơn:
✔ Thêm scoring rubric cố định
Không để LLM tự do chấm.
Ví dụ:
- Technical skill (40%)
- Experience relevance (30%)
- Communication clarity (15%)
- Problem solving (15%)
Thêm structured output
Yêu cầu LLM trả về:
{
"score": 7.5,
"technical": 8,
"experience": 6,
"communication": 7,
"missing": ["Docker", "CI/CD"]
}
Tách workflow thành 3 pipeline
- Audio pipeline
- Memory pipeline
- Evaluation pipeline
Đừng gộp tất cả vào một workflow — sẽ khó debug.
CẢNH BÁO THỰC TẾ
Bạn đang xây một hệ thống chấm điểm con người.
Nếu prompt không chuẩn:
→ LLM sẽ thiên vị
→ Hoặc đánh giá mơ hồ
Bạn phải:
- Viết rubric rõ
- Chuẩn hóa JD
- Chuẩn hóa câu hỏi phỏng vấn
Nếu không, hệ thống sẽ chỉ “nghe có vẻ thông minh” chứ không chính xác.
Thiết kế prompt đánh giá chuẩn HR
Nếu bạn muốn hệ thống chấm ứng viên nghiêm túc như HR thật, thì prompt không được chung chung kiểu “hãy đánh giá mức độ phù hợp”.
Bạn cần:
- Có rubric cố định
- Có trọng số rõ ràng
- Có format JSON bắt buộc
- Có nguyên tắc chống thiên vị
- Có logic trừ điểm
Dưới đây là prompt chuẩn để dùng trực tiếp trong Ollama local.
PROMPT ĐÁNH GIÁ ỨNG VIÊN (CHUẨN HR – STRUCTURED)
Bạn đưa vào LLM 4 phần:
- Job Description
- Rubric
- Transcript
- Hướng dẫn output
SYSTEM PROMPT
Bạn là chuyên gia tuyển dụng 15 năm kinh nghiệm.
Bạn đánh giá dựa trên bằng chứng cụ thể trong câu trả lời.
Không suy đoán.
Không đánh giá dựa trên giới tính, tuổi, vùng miền, giọng nói.
Chỉ đánh giá dựa trên nội dung thực tế.
Nếu thông tin không đủ, ghi rõ "không đủ dữ liệu".
Phải trả về JSON hợp lệ.
Không giải thích ngoài JSON.
RUBRIC CHUẨN (Có thể tái sử dụng cho mọi JD)
Thang điểm 0–10 cho mỗi tiêu chí.
1. Technical Competency (40%)
- 0–3: Không có kỹ năng liên quan
- 4–6: Có hiểu biết cơ bản
- 7–8: Thành thạo thực tế
- 9–10: Chuyên gia, có dẫn chứng cụ thể
2. Relevant Experience (25%)
- Đã làm việc tương tự?
- Có kết quả đo lường?
- Có vai trò chính hay phụ?
3. Problem Solving & Thinking (15%)
- Có tư duy cấu trúc?
- Có ví dụ thực tế?
- Có phân tích nguyên nhân–kết quả?
4. Communication Clarity (10%)
- Trả lời rõ ràng?
- Có cấu trúc?
- Có lan man?
5. Cultural / Role Fit (10%)
- Hiểu đúng vai trò?
- Phù hợp môi trường làm việc?
USER PROMPT TEMPLATE (DÙNG TRONG N8N)
JOB DESCRIPTION:
{{job_description}}
INTERVIEW QUESTION:
{{question}}
CANDIDATE ANSWER:
{{transcript}}
YÊU CẦU:
1. Trích dẫn bằng chứng cụ thể từ câu trả lời.
2. Không suy đoán những gì ứng viên không nói.
3. Nếu thiếu thông tin cho một tiêu chí, ghi rõ.
Trả về JSON đúng format:
{
"overall_score": number,
"technical_score": number,
"experience_score": number,
"problem_solving_score": number,
"communication_score": number,
"culture_fit_score": number,
"strengths": [],
"weaknesses": [],
"missing_skills": [],
"evidence": {
"technical": "",
"experience": "",
"problem_solving": "",
"communication": "",
"culture_fit": ""
},
"recommendation": "strong_yes | yes | borderline | no",
"confidence_level": "high | medium | low"
}
CÔNG THỨC TÍNH ĐIỂM
Trong prompt bạn thêm:
overall_score =
(technical_score * 0.4) +
(experience_score * 0.25) +
(problem_solving_score * 0.15) +
(communication_score * 0.1) +
(culture_fit_score * 0.1)
Làm tròn 1 chữ số thập phân.
Đừng để LLM tự tính lung tung.
PHIÊN BẢN CHẤM NHIỀU CÂU HỎI
Nếu bạn đánh giá cả buổi phỏng vấn:
Thêm phần:
Dựa trên toàn bộ câu trả lời, đánh giá xu hướng tổng thể:
- Có nhất quán không?
- Có phóng đại?
- Có mâu thuẫn giữa các câu?
Và thêm field:
"consistency_check": "",
"risk_flags": []
PHIÊN BẢN NÂNG CAO (ANTI-BULLSHIT MODE)
Thêm rule:
Nếu ứng viên nói chung chung (ví dụ: "em có kinh nghiệm nhiều năm"),
mà không có số liệu hoặc ví dụ cụ thể,
giảm 1–2 điểm Technical hoặc Experience.
PROMPT RÚT GỌN (CHO REALTIME)
Nếu bạn muốn phản hồi ngay sau mỗi câu trả lời:
Chấm riêng câu trả lời này cho tiêu chí:
- Technical
- Problem solving
- Communication
Không tính overall.
Trả JSON.
LỜI KHUYÊN THẲNG THẮN
Nếu bạn không ép LLM trả JSON + rubric rõ ràng:
→ Nó sẽ trả lời kiểu “Ứng viên khá tốt”
→ Không dùng được trong hệ thống tự động
Và nếu bạn không chuẩn hóa câu hỏi phỏng vấn:
→ Điểm sẽ dao động vô nghĩa
Gửi ý promte có thể AI gợi ý thêm:
- Prompt chuyên cho vị trí kỹ thuật (Developer)
- Prompt chuyên cho Sales
- Prompt chuyên cho vị trí quản lý
- Hoặc framework đánh giá theo competency model quốc tế
- Thiết kế chiến lược ranking nhiều ứng viên
- Hoặc tối ưu schema cho >1 triệu vector
Thiết kế schema Qdrant
Nếu bạn muốn hệ thống phỏng vấn local hoạt động ổn định, nhanh và mở rộng được, thì schema Qdrant phải thiết kế theo tư duy:
Tách collection theo mục đích
Tối ưu payload để filter nhanh
Chuẩn hóa vector dimension
Cho phép scoring + audit + multi-interview
Tôi sẽ thiết kế schema chuẩn production cho bạn.
TỔNG THỂ KIẾN TRÚC COLLECTION
Bạn nên có 3 collections riêng biệt:
job_descriptionsinterview_memorycandidate_profiles
Đừng gộp chung — sẽ khó filter và chậm.
COLLECTION: job_descriptions
Dùng để semantic search yêu cầu công việc.
Vector Config
- Size: 768 hoặc 1024 (tuỳ model embedding)
- Distance: Cosine
Ví dụ nếu dùng nomic-embed-text → 768
Payload Schema
{
"job_id": "string",
"job_title": "string",
"department": "string",
"chunk_index": "integer",
"content": "string",
"required_skills": ["string"],
"min_year_experience": "integer",
"level": "junior | mid | senior | lead",
"created_at": "timestamp"
}
Index tối ưu
Bạn phải tạo payload index cho:
- job_id (keyword)
- department (keyword)
- level (keyword)
Điều này cho phép filter trước khi search vector.
COLLECTION: interview_memory
Lưu từng câu trả lời ứng viên.
Vector Config
Same dimension như JD embedding.
Payload Schema
{
"candidate_id": "string",
"interview_id": "string",
"question_id": "string",
"question_text": "string",
"answer_text": "string",
"timestamp": "timestamp",
"technical_score": "float",
"confidence_level": "string",
"risk_flags": ["string"]
}
Tại sao phải lưu score vào payload?
Vì bạn có thể:
- Query những câu < 5 điểm
- Tìm pattern yếu
- Audit lại decision
Đừng chỉ lưu transcript.
COLLECTION: candidate_profiles
Lưu embedding tổng hợp toàn bộ buổi phỏng vấn.
Dùng để:
- So sánh nhiều ứng viên
- Ranking
- Talent pool search
Payload Schema
{
"candidate_id": "string",
"job_id": "string",
"overall_score": "float",
"technical_avg": "float",
"experience_avg": "float",
"problem_solving_avg": "float",
"communication_avg": "float",
"culture_fit_avg": "float",
"recommendation": "string",
"missing_skills": ["string"],
"strengths": ["string"],
"weaknesses": ["string"],
"interview_date": "timestamp"
}
VECTOR PARAMETERS TỐI ƯU
Khi tạo collection:
{
"vectors": {
"size": 768,
"distance": "Cosine"
},
"optimizers_config": {
"default_segment_number": 2
},
"hnsw_config": {
"m": 16,
"ef_construct": 100
}
}
Nếu bạn có < 100k vectors → config này đủ nhanh.
CHIẾN LƯỢC SEARCH TỐI ƯU
Khi đánh giá câu trả lời
- Embed answer
- Search trong
job_descriptions- Filter job_id
- Limit 5
- Pass 5 chunks vào LLM
Khi so sánh nhiều ứng viên
Embed “ideal candidate profile”
Search trong candidate_profiles
CHIẾN LƯỢC ID
Đừng dùng random UUID khó đọc.
Dùng format:
job_202602_dev_backend
cand_20260224_001
interview_20260224_001
Dễ audit.
MEMORY TỐI ƯU HÓA
Đừng để collection phình to vô nghĩa.
Sau mỗi buổi phỏng vấn:
- Tổng hợp lại thành 1 profile vector
- Có thể xóa chi tiết từng câu nếu không cần audit sâu
BẢO MẬT (VÌ BẠN MUỐN 100% LOCAL)
- Chạy Qdrant trong Docker network nội bộ
- Không expose port ra internet
- Không bật telemetry
- Không log transcript vào console
SAI LẦM PHỔ BIẾN
- Không tách collection
- Không index payload
- Không lưu metadata score
- Không filter trước khi vector search
- Dùng dimension sai với model embedding
TỐI ƯU HIỆU NĂNG
Nếu máy bạn RAM 24GB:
- Batch insert 100 vectors/lần
- Set
ef_search= 64 - Không cần quantization
Nếu sau này > 500k vectors:
→ bật scalar quantization
Collection: competency_framework
Lưu chuẩn năng lực nội bộ công ty.
Khi đánh giá:
- Search JD
- Search competency framework
- Merge → LLM đánh giá sâu hơn
Hệ thống sẽ giống ATS thực thụ.
Xây dựng trình mô phỏng phỏng vấn AI bằng LLaMA
prompt =
Bạn là một người phỏng vấn giàu kinh nghiệm đang tuyển dụng cho vị trí {role} .
Hãy tạo chính xác 5 câu hỏi phỏng vấn.
Bối cảnh:
– Trình độ kinh nghiệm: {experience}
– Hình thức phỏng vấn: {mode}
– Độ tự tin của ứng viên: {confidence}
– Kỹ năng cốt lõi: {skills}
Quy tắc:
– Câu hỏi phải hoàn toàn phù hợp với vai trò
– Kết hợp câu hỏi lý thuyết, thực hành và tình huống
– Sau mỗi câu hỏi, hãy thêm:
“Tại sao câu hỏi này được đặt ra: <một dòng ngắn>”
– Ngắn gọn
– Không có bình luận thêm
answer_prompt = f”””
Bạn là một người phỏng vấn chuyên nghiệp.
Đối với mỗi câu hỏi phỏng vấn dưới đây:
– Lặp lại câu hỏi
– Sau đó cung cấp một câu trả lời lý tưởng ngắn gọn (2-4 dòng)
Định dạng nghiêm ngặt:
Q: <câu hỏi>
A: <câu trả lời>
Không có bình luận bổ sung.

Bài viết liên quan: