Một dự án thực tiễn về Trí tuệ Nhân tạo Thế hệ mới (GenAI) giúp cải thiện CV và củng cố hồ sơ năng lực của bạn. Mộtcông cụ phản hồi CV bằng AI , xem xét CV như một nhà tuyển dụng, chỉ ra những điểm yếu và đề xuất các cải tiến.


bạn có thểsử dụng nó cho sơ yếu lý lịch của mình,
bạn có thểmở rộng nó,
và bạn có thểđưa nó vào sơ yếu lý lịch của mình như một dự án GenAI.
Đây là một cơ hội thắng lợi cực lớn dành cho bạn, miễn là bạn làm theo hướng dẫn này.
Chúng ta sẽ cùng nhau xây dựng nó.

Trước khi viết bất kỳ đoạn mã nào, hãy thành thật với chính mình.
Hầu hết chúng ta đều có:
- Đã nộp đơn xin việc vào hàng chục vị trínhưng không nhận được phản hồi nào.
- Cập nhật sơ yếu lý lịch một cách mù quáng
- Tôi đã sao chép các mẫu với hy vọng chúng sẽ hoạt động.
Và sau tất cả, tôi vẫn không nhận được phản hồi thực sự nào.
Vấn đề không phải là kỹ năng. Vấn đề làphản hồi.
Các nhà tuyển dụng không nói cho bạn biết vấn đề là gì.
Hệ thống ATS sẽ âm thầm từ chối bạn.
Vì vậy, thay vì đoán mò, chúng tôi xây dựng một công cụ chobiết chính xác cần sửa chữa ở đâu.
Khi dự án này kết thúc, chúng ta sẽ có một công cụ:
- Chấp nhận hồ sơ xin việc dạng PDF.
- Trích xuất nội dung
- Sử dụng mô hình GenAI để phân tích giống như một nhà tuyển dụng.
- Trả vềphản hồi có cấu trúc, có thể thực hiện được, chứ không phải lời khuyên chung chung.
Đặc trưng:
- Những điểm mạnh cần giữ lại
- Những điểm yếu cần cải thiện
- Các bộ phận bị thiếu
- Sự thân thiện của ATS
- Các bước tiếp theo cần thực hiện
Đây không phải là một dự án ngẫu nhiên.
Đây là mộtcông cụ đánh giá thông minh hồ sơ xin việc.
Bước 1: Cấu trúc dự án
Tạo một dự án Python mới (tôi đang dùng PyCharm, nhưng bất kỳ trình soạn thảo nào cũng được).
Cấu trúc thư mục cuối cùng của chúng ta sẽ trông như thế này:
resume_ai_analyzer/
│
├── app.py
├── resume_parser.py
├── feedback_engine.py
├── prompts.py
├── requirements.txt
└── .envMỗi tập tin đều cómột trách nhiệm rõ ràng:
app.py→ Giao diện người dùng (Streamlit)resume_parser.py→ Trích xuất văn bản từ bản PDF sơ yếu lý lịchfeedback_engine.py→ Liên hệ với LLM (Bộ phận GenAI)prompts.py→ Kỹ thuật nhanh chóng.env→ Khóa API (bảo mật) (Chúng tôi sẽ sử dụng khóa API miễn phí)
Sự phân tách này rất quan trọng.
Mở cửa sổ dòng lệnh và cài đặt các thư viện cần thiết:
pip install streamlit pymupdf python-dotenv groqBây giờ hãy thêm chúng vàorequirements.txt:
streamlit
pymupdf
python-dotenv
groqTại sao lại là những thư viện này?
- Streamlit→ xây dựng giao diện người dùng nhanh chóng
- PyMuPDF→ trích xuất văn bản từ sơ yếu lý lịch dạng PDF
- python-dotenv→ tải các biến môi trường một cách an toàn
- Groq→ chạy các mô hình LLaMA thông qua API
Tạo một tệp có tên.envvà thêm nội dung sau vào đó:
GROF_API_KEY =your_groq_api_key_hereBạn có thể tạo Khóa API của mình tại đây:Khóa API Groq
Hoàn toàn miễn phí!
Vì sao điều này lại quan trọng:
- Không bao giờ mã hóa cứng khóa API.
- Đảm bảo an toàn cho dự án của bạn.
Hãy cùng định nghĩacáchtrí tuệ nhân tạo nên suy nghĩ.
Tạo nênprompts.py:
RESUME_FEEDBACK_PROMPT = """
Bạn là một chuyên gia tuyển dụng kỹ thuật và đánh giá sơ yếu lý lịch.
Hãy phân tích sơ yếu lý lịch bên dưới và cung cấp phản hồi theo định dạng có cấu trúc sau:
1. Tóm tắt tổng quan (2-3 dòng)
2. Điểm mạnh
3. Điểm yếu
4. Các phần thiếu hoặc chưa được thể hiện đầy đủ
5. Cải thiện gạch đầu dòng (viết lại 2 gạch đầu dòng yếu)
6. Điểm thân thiện với hệ thống ATS (1-10) kèm lý do
7. Đề xuất hành động cuối cùng
Nội dung sơ yếu lý lịch:
----------------
{resume_text}
"""
Lý do tại sao lời nhắc này hiệu quả
- Chúng tôichỉ định một vai trò(“chuyên viên tuyển dụng”).
- Chúng taép buộc cấu trúc
- Kết quả đầu ra trở nên dễ dự đoán và dễ đọc.
- Đây là kỹ thuật xử lý sự cố nhanh chóng, chứ không chỉ đơn thuần là nhắc nhở.
Bước 5: Trích xuất văn bản từ PDF sơ yếu lý lịch

Bây giờ chúng ta hãy trích xuất văn bản từ bản lý lịch đã tải lên.
Tạo nênresume_parser.py:
import fitz # PyMuPDF
def extract_text_from_pdf ( uploaded_file ):
"""
Trích xuất văn bản từ sơ yếu lý lịch PDF đã tải lên.
"""
text = ""
# Mở PDF từ luồng trong bộ nhớ
pdf_document = fitz.open ( stream
=uploaded_file.read(),
filetype= "pdf"
)
# Lặp qua từng trang
for page in pdf_document:
text += page.get_text()
return text.strip()Chuyện gì đang xảy ra ở đây vậy?
- Streamlit tải các tệpvào bộ nhớ, chứ không phải dưới dạng đường dẫn tệp.
- PyMuPDF đọc từng trang của tệp PDF.
- Chúng tôi trích xuất văn bản dễ đọc (không phải hình ảnh)
- Văn bản này trở thành dữ liệu đầu vào cho trí tuệ nhân tạo (AI).
Đây chính là lúc GenAI phát huy tác dụng.
Tạo nênfeedback_engine.py:
import os
from dotenv import load_dotenv from groq import Groq
from prompts import RESUME_FEEDBACK_PROMPT # Tải các biến môi trường load_dotenv () # Khởi tạo client Groq client = Groq(api_key=os.getenv( "GROQ_API_KEY" )) def generate_feedback ( resume_text ): """ Gửi văn bản sơ yếu lý lịch đến LLaMA và trả về phản hồi có cấu trúc. """ prompt = RESUME_FEEDBACK_PROMPT. format ( resume_text=resume_text ) response = client.chat.completions.create( model= "llama-3.1-8b-instant" , messages=[ { "role" : "system" , "content" : "Bạn là chuyên gia tuyển dụng kỹ thuật." }, { "role" : "user" , "content" : prompt } ], temperature= 0.4 , max_tokens= 800 ) return response.choices[ 0 ].message.content
Giải thích các quyết định quan trọng
- Nhiệt độ thấp (0,4)→ phản hồi nhất quán, giống như của nhà tuyển dụng
- Câu hỏi được cấu trúc hợp lý→ kết quả chất lượng cao
- Đây là cách các hệ thống GenAI thực sự được kết nối.
Bước 7: Giao diện người dùng Streamlit (Tổng hợp tất cả các thành phần)
Giờ đây chúng ta kết nối mọi thứ lại với nhau.
Tạo nênapp.py:
import streamlit as st
from resume_parser import extract_text_from_pdf
from feedback_engine import generate_feedback
# 1. Cấu hình Trang (Phải là trang đầu tiên)
st.set_page_config(
page_title= "ResumeAI - Phản hồi Chuyên nghiệp" ,
page_icon= "🚀" ,
layout= "centered"
)
# 2. Cải tiến Giao diện Người dùng Hiện đại (CSS)
st.markdown( """
<style>
/* Nhập Phông chữ Google: Inter */
@import url('//fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap');
/* Cài đặt Toàn cầu */
html, body, [class*="css"] {
font-family: 'Inter', sans-serif;
}
/* Nền Gradient Tối Hiện đại */
.stApp {
background: radial-gradient(circle at 50% -20%, #2e1065, #0f172a 40%, #020617 100%);
color: #f8fafc;
}
/* Văn bản tiêu đề chuyển sắc */
.gradient-text {
background: linear-gradient(90deg, #818cf8 0%, #c084fc 50%, #f472b6 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: 700;
font-size: 3rem;
text-align: center;
letter-spacing: -1px;
}
/* Kiểu phụ đề */
.subtitle {
color: #94a3b8;
text-align: center;
font-size: 1.1rem;
margin-bottom: 2.5rem;
font-weight: 300;
}
/* Thẻ Glassmorphism */
.glass-card {
background: rgba(30, 41, 59, 0.4);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 24px;
padding: 2.5rem;
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.3);
margin-bottom: 2rem;
}
/* Khu vực tải lên tập tin được định kiểu */
[data-testid="stFileUploader"] section {
background-color: rgba(255, 255, 255, 0.03);
border: 1px dashed #64748b;
border-radius: 12px;
padding: 1rem;
}
/* Định kiểu nút hiện đại */
.stButton > button {
background: linear-gradient(92deg, #6366f1 0%, #8b5cf6 100%);
màu: trắng;
độ đậm chữ: 600;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 12px;
width: 100%;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(99, 102, 241, 0.3);
text-transform: uppercase;
letter-spacing: 0.5px;
font-size: 0.9rem;
}
.stButton > button:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(99, 102, 241, 0.5);
}
/* Hoạt ảnh kết quả & Khung */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); } }
}
.feedback-container {
animation: fadeIn 0.8s ease-out;
background: rgba(15, 23, 42, 0.6);
border-left: 4px solid #8b5cf6;
padding: 2rem;
border-radius: 12px;
margin-top: 2rem;
}
.feedback-header {
font-size: 1.2rem;
font-weight: 600;
color: #e2e8f0;
margin-bottom: 1rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
/* Dọn dẹp phần đệm mặc định của Streamlit */
.block-container {
padding-top: 3rem;
padding-bottom: 3rem;
}
</style>
""" , unsafe_allow_html= True )
# 3. Phần tiêu đề
st.markdown( '<h1 class="gradient-text">ResumeAI</h1>' , unsafe_allow_html= True )
st.markdown(
'<p class="subtitle">Trí tuệ sơ yếu lý lịch được hỗ trợ bởi AI. Tải lên PDF của bạn để mở khóa thông tin chi tiết từ nhà tuyển dụng chuyên nghiệp.</p>' ,
unsafe_allow_html= True )
# 4. Thẻ giao diện chính
với st.container():
st.markdown( '<div class="glass-card">' , unsafe_allow_html= True )
# Nhãn tùy chỉnh cho trình tải lên
st.markdown( "##### 📂 Tải lên sơ yếu lý lịch" , unsafe_allow_html= True )
uploaded_file = st.file_uploader(
"Tải lên sơ yếu lý lịch (chỉ PDF)" , # Nhãn được ẩn bằng CSS lý tưởng, nhưng vẫn giữ lại để dễ tiếp cận
type =[ "pdf" ],
label_visibility= "collapsed"
)
nếu uploaded_file:
st.markdown("<div style='height: 15px'></div>" , unsafe_allow_html= True ) # Khoảng cách
# Nút Phân tích
nếu st.button( "Tạo phản hồi chuyên nghiệp" ):
với st.spinner( "🤖 Phân tích cú pháp, tác động và từ khóa..." ):
# --- LOGIC PHẦN MỀM (Không thay đổi) ---
resume_text = extract_text_from_pdf(uploaded_file)
feedback = generate_feedback(resume_text)
# ---------------------------------
# 5. Hiển thị kết quả
st.markdown( """
<div class="feedback-container">
<div class="feedback-header">
<span>✨ Kết quả phân tích</span>
</div>
""" , unsafe_allow_html= True )
# Sử dụng st.markdown cho nội dung để đảm bảo định dạng markdown (in đậm, danh sách) hiển thị chính xác
st.markdown(feedback)
st.markdown( "</div>" , unsafe_allow_html= True )
st.markdown( '</div>' , unsafe_allow_html= True ) # Kết thúc glass-card
# Chân trang
st.markdown( """
<div style="text-align: center; color: #475569; margin-top: 2rem; font-size: 0.8rem;">
BÍ MẬT • CÔNG CỤ PHÂN TÍCH SƠ YẾU LÝ LỊCH AI
</div>
""" , unsafe_allow_html= True )Hiểu về giao diện người dùng (mà không bị lạc lối trong CSS)
Đoạn mã giao diện người dùng ở trên có vẻ dài, nhưng ý tưởng đằng sau nó thực ra rất đơn giản.
Thay vì giải thích từng quy tắc CSS, chúng ta hãy chia nhỏ nó thànhnhững phần quan trọng.
A) Cấu hình trang và định danh ứng dụng
Ở phần đầu, chúng ta thiết lập cấu hình trang:
- Tên và biểu tượng ứng dụng.
- Bố cục căn giữa.
Điều này đảm bảo ứng dụng mang lại cảm giác như mộtsản phẩm hoàn chỉnh, chứ không phải chỉ là một bản demo nhanh.
B) Chủ đề tối tùy chỉnh và kiểu dáng trực quan
Thay vì sử dụng giao diện mặc định của Streamlit, chúng tôi chènCSS tùy chỉnhđể đạt được:
- Nền chuyển màu tối (không phải màu đen nhàm chán).
- Kiểu chữ hiện đại sử dụng Google Fonts.
- Các tấm thiệp theo phong cách Glassmorphism.
Giao diện người dùng tốt rất quan trọng nếu bạn muốn trình bày dự án cho nhà tuyển dụng hoặc người dùng.
C) Quy trình người dùng đơn giản (Tải lên → Phân tích → Kết quả)
Giao diện người dùng tuân theo một luồng thiết kế rất có chủ đích:
- Tải lên sơ yếu lý lịch (PDF).
- Nhấp chuột vào một nút hành động rõ ràng.
- Xem kết quả được trình bày dưới dạng hoạt ảnh và có cấu trúc.
Không có sự nhầm lẫn hay các bước không cần thiết.
Điều này giúp tập trung vàonhững gì người dùng thực sự muốn.
D) Trình bày phản hồi (Dễ đọc > Đẹp mắt)
Kết quả được hiển thị bên trong một vùng chứa chuyên dụng với:
- Hiệu ứng hoạt hình tinh tế.
- Xóa tiêu đề.
- Định dạng Markdown.
Điều này giúp cho các phản hồi dài do AI tạo ra trở nên dễ đọc hơn.
Hãy nhớ:UX không phải để phô trương mà là để giảm thiểu khó khăn.
Tóm tắt quy trình
- Tải lên sơ yếu lý lịch.
- Trích xuất văn bản.
- Gửi đến LLaMA.
- Hiển thị phản hồi có cấu trúc.
Vậy thôi.
Từ thiết bị đầu cuối:
streamlit run app.pyTải lên sơ yếu lý lịch của bạn ở định dạng PDF và xem kết quả.
Bằng cách xây dựng điều này:
- Bạn tự cải thiệnsơ yếu lý lịch của mình.
- Bạn sẽ học vềthiết kế hệ thống GenAI.
- Bạn sẽ có mộtdự án thực tếđể đưa vào hồ sơ năng lực của mình.
- Bạn có thể mở rộng phần này tùy ý.
Đây chính xác là loại dự án giúp ích cho người dùng, giúp phát triển sự nghiệp của bạn và dạy cho bạn những kỹ năng thực tế.
Nguồn: //medium.com/activated-thinker/build-an-ai-resume-analyzer-using-genai-f075c6fd73f6

Bài viết liên quan: