Code AI phải qua khâu kiểm vải

Code AI phải qua khâu kiểm vải

Đừng deploy code do model sinh ra như copy từ chat vào repo. Đây là playbook dựng pipeline generate–validate–rerank cho team builder.

11 giờ đêm, một bạn dev trong team giả định của mình — gọi là Linh — nhắn vào Slack: “Model viết được function rồi chị ơi, mai gắn vào workflow luôn nhé?”

Mình nhìn đoạn code sinh ra: chạy được với case mẫu, tên biến sạch sẽ, comment cũng ngoan. Nhưng có một dòng eval() nằm lấp ló như mũi chỉ thừa ở mép áo. Nếu chỉ demo nội bộ thì cười cho qua. Nếu nó đi vào pipeline xử lý dữ liệu khách hàng thì mai cả team có buổi họp rất dài.

Luận điểm của bài này đơn giản: đừng đánh giá code-generation model bằng câu hỏi “nó viết được không”, hãy đánh giá bằng “team mình có đủ đường ray để lọc, chấm, và chọn code tốt hơn không”.

Salesforce CodeGen trong tutorial gần đây là một cái cớ hay để nói chuyện này. Không phải vì CodeGen là lựa chọn mặc định cho mọi team, mà vì workflow quanh nó rất đáng học: generate nhiều candidate, extract function, check syntax, scan safety, chạy unit test, rồi rerank. Với builder, phần đáng tiền không nằm ở model sinh chữ; nó nằm ở khâu kiểm vải trước khi cắt may vào sản phẩm.

Sơ đồ minh họa cho bài Code AI phải qua khâu kiểm vải

Sơ đồ tóm tắt ý chính của bài viết.

Cảnh của Linh: model không sai, quy trình mới thiếu

Team Linh đang xây một internal coding assistant cho nhóm data. Người dùng nhập yêu cầu kiểu:

“Viết hàm Python nhận list order và trả về tổng doanh thu theo từng ngày.”

Model sinh ra function. Có lúc đúng. Có lúc sai edge case. Có lúc import thư viện lạ. Có lúc viết code chạy được nhưng phức tạp quá mức. Nếu team chỉ lấy output đầu tiên rồi đưa vào workflow, họ đang để model tự làm cả ba vai: thợ viết code, reviewer, và người quyết định merge.

Nói thẳng ra thì: code generation trên production không phải một lệnh autocomplete dài hơn. Nó là một pipeline tuyển chọn.

Một pipeline tối thiểu nên có các lớp sau:

  1. Generation — sinh nhiều phương án từ cùng một prompt.
  2. Function extraction — tách phần function thật khỏi text lan man.
  3. Syntax validation — kiểm tra code có parse được không.
  4. Static safety checks — rà dấu hiệu nguy hiểm mà chưa cần chạy code.
  5. Restricted execution — chạy trong môi trường bị giới hạn quyền.
  6. Unit-test validation — kiểm tra hành vi bằng test case.
  7. Reranking — xếp hạng candidate theo điểm đúng, an toàn, đơn giản.

Đây là chỗ nhiều team hiểu sai: model mới hơn chưa chắc cứu được pipeline mỏng. Một model mạnh vẫn có thể sinh code nguy hiểm nếu prompt mở quá rộng, test thiếu, hoặc sandbox hở.

Framework 3 lớp: sinh, lọc, chọn

Nếu bạn đang là tech lead, đừng bắt đầu bằng câu hỏi “dùng model nào?”. Bắt đầu bằng ba lớp quyết định này.

| Lớp | Câu hỏi cần trả lời | Nếu bỏ qua thì sao |
|---|---|---|
| Sinh | Ta cần bao nhiêu candidate cho một task? | Output đầu tiên thành định mệnh |
| Lọc | Candidate nào bị loại ngay vì sai hoặc rủi ro? | Code xấu lọt vào vòng chấm điểm |
| Chọn | Candidate nào đáng dùng nhất theo tiêu chí team? | Model thắng nhờ may mắn, không nhờ chất lượng |

Best-of-N là cách sinh N phương án rồi chọn phương án tốt nhất theo tiêu chí định sẵn. Nó giống đặt nhiều mảnh vải lên bàn rồi mới chọn miếng ít lỗi nhất để may, thay vì nhắm mắt lấy mảnh trên cùng.

Nhưng best-of-N chỉ có nghĩa khi bạn có bộ lọc tốt. Nếu không, bạn chỉ đang sinh thêm rác nhanh hơn.

Ví dụ cụ thể: với task viết function Python nhỏ, điểm rerank có thể gồm:

Đừng biến scoring thành thần học. Nó chỉ cần đủ rõ để team tranh luận được: “Tại sao candidate A thắng candidate B?”

Một buổi dựng thử: pipeline nhỏ nhưng có răng

Bạn có thể thử trong một buổi làm việc, không cần dựng platform hoành tráng. Mục tiêu là tạo một harness — bộ khung chạy thử có kiểm soát — cho code do model sinh.

Bước 1: khóa phạm vi task

Đừng bắt đầu bằng “AI viết mọi code Python”. Hãy chọn một lớp việc nhỏ:

Ví dụ prompt:

Write a Python function named group_revenue_by_day(orders).
Each order is a dict with keys: date, amount.
Return a dict mapping date to total amount.
Do not use external libraries.

Bước 2: sinh nhiều candidate, nhưng lưu raw output

Dù bạn dùng CodeGen từ Hugging Face, model local qua SGLang, hay API model có tham số effort, hãy lưu lại:

Đây là artifact export — xuất hiện vật chứng kỹ thuật để debug lại sau. Khi một candidate fail, bạn cần biết nó fail vì prompt, model, hay bộ test.

Bước 3: extract function trước khi chấm

Model thường trả lời kèm giải thích. Đừng chạy nguyên blob text. Hãy tách function bằng parser hoặc rule rõ ràng.

import ast


def is_valid_python(code: str) -> bool:
    try:
        ast.parse(code)
        return True
    except SyntaxError:
        return False

ast.parse chỉ cho biết code hợp cú pháp, không nói code đúng hay an toàn. Nhưng nó là hàng rào đầu tiên.

Bước 4: static safety check trước khi execute

Static safety check là kiểm tra rủi ro trên mã nguồn mà chưa chạy nó. Với code do AI sinh, lớp này nên chặn tối thiểu:

BANNED_NAMES = {"eval", "exec", "open", "compile", "__import__"}
BANNED_MODULES = {"os", "subprocess", "socket", "requests"}


def static_safety_check(code: str) -> list[str]:
    findings = []
    tree = ast.parse(code)

    for node in ast.walk(tree):
        if isinstance(node, ast.Call) and isinstance(node.func, ast.Name):
            if node.func.id in BANNED_NAMES:
                findings.append(f"Banned call: {node.func.id}")

        if isinstance(node, ast.Import):
            for alias in node.names:
                if alias.name.split(".")[0] in BANNED_MODULES:
                    findings.append(f"Banned import: {alias.name}")

    return findings

Đây không thay thế security review. Nhưng nó giúp loại các mũi chỉ lỗi quá rõ trước khi code được chạy.

Bước 5: chạy trong restricted execution

Restricted execution là chạy code trong môi trường giới hạn quyền. Với prototype, bạn có thể giới hạn builtins và timeout. Với production, nên dùng sandbox/container riêng, quota CPU/RAM, không network mặc định.

import signal


def run_with_timeout(fn, seconds=2):
    def handler(signum, frame):
        raise TimeoutError("candidate timed out")

    signal.signal(signal.SIGALRM, handler)
    signal.alarm(seconds)
    try:
        return fn()
    finally:
        signal.alarm(0)

Timeout không phải chi tiết phụ. Một function sinh sai có thể loop vô hạn, và lúc đó “chỉ là code nhỏ” sẽ thành ticket hạ tầng.

Bước 6: unit test là hợp đồng, không phải trang trí

Unit-test validation là dùng test case để xác nhận hành vi mong muốn. Với mỗi task, viết cả happy path lẫn edge case.

def test_group_revenue_by_day(fn):
    assert fn([]) == {}
    assert fn([
        {"date": "2026-06-18", "amount": 10},
        {"date": "2026-06-18", "amount": 5},
        {"date": "2026-06-19", "amount": 7},
    ]) == {
        "2026-06-18": 15,
        "2026-06-19": 7,
    }

Nếu task không viết được test, task đó chưa nên giao cho code-generation pipeline tự động. Ít nhất là chưa nên cho chạy gần dữ liệu thật.

Rerank: đừng để output đầu tiên cầm kéo

Sau khi có nhiều candidate, bạn cần một scoring function. Đừng quá tham. Phiên bản đầu có thể như sau:

def score_candidate(result):
    score = 0
    if result["syntax_ok"]:
        score += 1
    if result["safety_findings"] == []:
        score += 2
    if result["tests_passed"]:
        score += 5
    score -= min(result["complexity"], 5) * 0.2
    return score

Reranking là xếp lại candidate sau khi đã có bằng chứng. Điểm quan trọng: candidate thắng không phải vì nó “trông hay”, mà vì nó qua các lớp kiểm tra bạn đã định nghĩa.

Ở đây có một quyết định kỹ thuật đáng nói: nếu bạn dùng model API có tham số kiểu effort — mức “cố gắng” hay lượng reasoning model bỏ ra — đừng bật cao cho mọi task. Task nhỏ có thể dùng effort thấp, sinh nhiều candidate rẻ hơn, rồi để unit test chọn. Task khó, nhiều edge case, mới đáng tăng effort. Câu hỏi không phải model có thông minh không, mà là bạn muốn trả tiền cho reasoning ở đâu trong pipeline.

Nếu chạy local model qua serving endpoint như SGLang, tradeoff lại khác: bạn kiểm soát hạ tầng tốt hơn, nhưng phải tự lo GPU, Docker, capacity, logging, và latency. Với team Việt Nam quy mô nhỏ, mình sẽ không vội self-host model lớn chỉ để viết vài function nội bộ. Hãy self-host khi dữ liệu, chi phí dài hạn, hoặc yêu cầu offline thật sự đẩy bạn đến đó.

Pitfall: security scanner không thay reviewer

Có một cám dỗ rất phổ biến: thêm static analyzer, export SARIF, gắn vào CI, rồi nghĩ xong chuyện. SARIF là định dạng báo cáo để tool security/CI đọc được; nó rất hữu ích để đưa finding vào workflow dev. Nhưng báo cáo đẹp không có nghĩa hệ thống an toàn.

SkillSpector-style scanning cho AI skills gợi ý một hướng đúng: tạo corpus gồm case lành và case cố tình độc hại, scan, gom finding, xuất báo cáo. Áp dụng sang code generation, bạn cũng nên có “bộ vải lỗi” của riêng mình:

Nếu scanner không bắt được những lỗi bạn đã biết, đừng đem nó đi bắt lỗi bạn chưa biết.

Nếu là mình, mình sẽ triển khai thế này

Với một team builder đang muốn đưa code AI vào workflow thật, mình sẽ chọn lộ trình ba pha:

Pha 1 — Offline harness. Chạy trên bộ task cố định, không nối vào repo chính. Mục tiêu là đo xem pipeline lọc được gì, fail ở đâu.

Pha 2 — Human-in-the-loop. Candidate tốt nhất được gửi thành draft PR hoặc snippet đề xuất. Người vẫn review và merge. Mục tiêu là giảm thời gian viết boilerplate, không thay review.

Pha 3 — CI gate. Chỉ cho tự động hóa với nhóm task hẹp, test mạnh, sandbox rõ, log đầy đủ. Mọi candidate đều có artifact để truy vết.

Sau bài này, thứ mình muốn bạn nghĩ khác là: đừng chọn code-generation model như chọn cây bút viết nhanh; hãy chọn cả quy trình kiểm đường may quanh nó. Model có thể sinh code trong vài giây, nhưng production cần biết mũi nào được giữ, mũi nào phải tháo ra.

AI viết code không đáng sợ; đáng sợ là mình để nó cầm kim khâu thẳng vào áo đang mặc.

---
Bụi Wire — nghiện đọc release notes lúc 2 giờ sáng

Nguồn tham khảo