ai newsFr., 16. Okt.
EilmeldungMistral-Modell Timestral läuft auf CPUs und nutzt Modified-MIT-Lizenz mit Umsatzgrenze

Trust, but Verify: Building a Claim Checker for AI Coding Agents

Tuần trước mình giao cho một coding agent sửa bug trong module thanh toán. Mười phút sau nó báo cáo rất tự tin: "Đã sửa billing/invoice.py , thêm test cho edge case, tất cả 142 tests đều pass." Mình mở terminal ra kiểm tra: file invoice.py không hề bị đổi, test mới nằm trong một file mà pytest khôn

dev.toFreitag 01:18 · 16.10.7 Min. Lesezeit
Bild: dev.to

Tuần trước mình giao cho một coding agent sửa bug trong module thanh toán. Mười phút sau nó báo cáo rất tự tin: "Đã sửa billing/invoice.py, thêm test cho edge case, tất cả 142 tests đều pass." Mình mở terminal ra kiểm tra: file invoice.py không hề bị đổi, test mới nằm trong một file mà pytest không collect, còn 3 test cũ thì fail. Agent không cố tình nói dối. Chỉ là nó mô tả việc nó định làm chứ không phải việc nó đã làm.

Trên Dev.to đang có bài khá hot về chuyện làm "lie detector" cho AI coding agent, và mình thấy vấn đề này rất thật với team ở Việt Nam, khi ngày càng nhiều bạn chạy Claude Code, Cursor, Aider hay Codex CLI ở chế độ tự động. Bài này chia sẻ cách mình dựng một claim checker nhỏ (~100 dòng Python) để đối chiếu báo cáo của agent với trạng thái thật của repo, trước khi mình phải review bằng mắt.

Cả 3 lý do đều dẫn tới một nguyên tắc: đừng tin bản tóm tắt, hãy tin git và exit code. Bản tóm tắt của agent chỉ là claim, mà claim thì cần được verify.

flowchart LR A[Agent hoàn thành task] --> B[Báo cáo summary] B --> C[Claim Extractor] C --> D{Verifier} D -->|git diff| E[Files thay đổi thật] D -->|pytest| F[Kết quả test thật] D -->|ruff| G[Lint thật] E & F & G --> H{Khớp với claim?} H -->|Có| I[Cho phép review / merge] H -->|Không| J[Reject + gửi lại cho agent] Bước 1: Trích xuất claim từ báo cáo Mình không dùng LLM để parse báo cáo, vì dùng LLM kiểm tra LLM thì lại thêm một lớp có thể sai. Regex đơn giản là đủ, vì agent hay viết summary theo vài pattern khá cố định: liệt kê file đã sửa (thường bọc trong backtick), và câu kiểu "all tests pass" / "X tests passed".

Mình yêu cầu agent (qua system prompt hoặc file AGENTS.md ) ghi summary vào.agent/report.md ở cuối mỗi task. Sau đó script này đọc file đó:

# claim_checker.py - Python 3.11+ import re import subprocess import sys from dataclasses import dataclass, field from pathlib import Path FILE_RE = re. compile ( r ' `([\w./-]+\.(?:py|ts|js|go|sql|yaml|toml))` ' ) TEST_PASS_RE = re. compile ( r ' (all tests pass|tests? (?:are |đều )?pass|(\d+) (?:tests )?passed) ', re. I ) @dataclass class Claims: files: set [ str ] = field ( default_factory = set ) tests_pass: bool = False test_count: int | None = None def extract_claims ( report: str ) -> Claims: c = Claims () c. files = set ( FILE_RE. findall ( report )) m = TEST_PASS_RE. search ( report ) if m: c. tests_pass = True if m. group ( 2 ): c. test_count = int ( m. group ( 2 )) return c Enter fullscreen mode Exit fullscreen mode Điểm mấu chốt: chỉ bắt những claim kiểm chứng được. Những câu như "code đã clean hơn" hay "xử lý mọi edge case" thì bỏ qua, mấy cái đó để người review.

Phần verify dùng đúng những tool bạn đang có sẵn: git diff để xem file nào thật sự thay đổi so với base branch, pytest để chạy lại test từ đầu (không dùng cache, không tin output cũ).

def sh ( cmd: list [ str ]) -> tuple [ int, str ]: p = subprocess. run ( cmd, capture_output = True, text = True ) return p. returncode, p. stdout + p. stderr def changed_files ( base: str = ' origin/main ' ) -> set [ str ]: _, out = sh ([ ' git ', ' diff ', ' --name-only ', f ' { base }...HEAD ' ]) _, staged = sh ([ ' git ', ' diff ', ' --name-only ' ]) # cả thay đổi chưa commit return set (( out + staged ). split ()) def run_tests () -> tuple [ bool, int ]: code, out = sh ([ ' pytest ', ' -q ', ' -p ', ' no:cacheprovider ' ]) m = re. search ( r ' (\d+) passed ', out ) return code == 0, int ( m. group ( 1 )) if m else 0 def verify ( claims: Claims ) -> list [ str ]: problems = [] real = changed_files () for f in claims. files - real: if Path ( f ). exists (): problems. append ( f ' Claim sửa { f } nhưng git diff không thấy thay đổi ' ) else: problems. append ( f ' Claim nhắc tới { f } nhưng file không tồn tại ' ) if claims. tests_pass: ok, passed = run_tests () if not ok: problems. append ( ' Claim tests pass nhưng pytest exit code!= 0 ' ) if claims. test_count and claims. test_count!= passed: problems. append ( f ' Claim { claims. test_count } passed, thực tế { passed } ' ) return problems if __name__ == ' __main__ ': report = Path ( '.agent/report.md ' ). read_text ( encoding = ' utf-8 ' ) issues = verify ( extract_claims ( report )) for i in issues: print ( f ' ❌ { i } ' ) sys. exit ( 1 if issues else 0 ) Enter fullscreen mode Exit fullscreen mode Có một chi tiết nhỏ nhưng quan trọng: -p no:cacheprovider. Nếu không tắt cache, một số plugin như pytest --lf có thể khiến bạn chỉ chạy lại test fail lần trước, và kết quả "pass" lúc đó không có nghĩa gì cả.

Ngoài ra mình còn check ngược: file nào có trong diff nhưng agent không nhắc tới. Cái này không tính là lỗi, nhưng mình in ra warning, vì agent hay "tiện tay" sửa pyproject.toml hoặc lock file mà không báo.

Script chỉ hữu ích khi nó chạy tự động. Mình gắn nó ở 2 chỗ: chạy local ngay sau khi agent kết thúc task, và trong CI khi PR có label agent-generated.

#!/usr/bin/env bash # scripts/agent-done.sh - chạy sau mỗi task của agent set -euo pipefail if [[! -f.agent/report.md ]]; then echo "Agent không ghi report -> reject" exit 1 fi ruff check. --quiet || { echo "Lint fail"; exit 1; } if! python3 claim_checker.py >.agent/verify.log 2>&1; then echo "Claim không khớp thực tế:" cat.agent/verify.log # gửi lại log cho agent để nó tự sửa, tối đa 2 vòng exit 2 fi echo "✅ Claims verified, sẵn sàng cho human review" Enter fullscreen mode Exit fullscreen mode Với Claude Code, bạn có thể gắn script này vào hook Stop trong.claude/settings.json. Exit code khác 0 sẽ chặn agent kết thúc và đẩy log lại cho nó. Với Aider hay các tool khác, chỉ cần wrap lệnh chạy agent trong một script shell rồi gọi agent-done.sh ở cuối.

sequenceDiagram participant Dev participant Agent participant Hook as Stop Hook participant Repo as git + pytest Dev->>Agent: Giao task Agent->>Repo: Sửa code, chạy lệnh Agent->>Hook: Kết thúc + report.md Hook->>Repo: git diff, pytest, ruff Repo-->>Hook: Kết quả thật alt Không khớp Hook-->>Agent: verify.log (làm lại) else Khớp Hook-->>Dev: Sẵn sàng review end Lưu ý giới hạn vòng lặp: nhớ giới hạn số lần retry (mình để 2). Nếu không, agent có thể loay hoay mãi, và tệ hơn là nó "học" cách viết report cho khớp với checker thay vì sửa code thật. Ví dụ nó sẽ bỏ hẳn câu "tests pass" khỏi report. Vì vậy với các task có sửa code, mình bắt buộc report phải có phần test, thiếu là reject luôn.

Phải nói rõ là checker này chỉ bắt được sai lệch giữa lời nói và hành động, không bắt được code sai logic. Test pass vẫn có thể là test vô nghĩa (kiểu assert True ), diff đúng file vẫn có thể là sửa sai chỗ. Nó là lớp lọc đầu tiên, rẻ và nhanh, giúp bạn khỏi mất 15 phút review một PR mà ngay từ đầu đã không chạy được.

Sau 2 tháng chạy trong team, khoảng 1/8 số task agent làm bị checker bắt lại, chủ yếu là lỗi tool edit fail âm thầm và test file đặt sai chỗ. Đó là số lần mình không còn phải tự mở terminal ra kiểm tra từng cái nữa.

Bắt đầu từ việc nhỏ nhất: hôm nay thêm một dòng git diff --name-only vào cuối workflow của agent rồi so với những gì nó báo cáo. Bạn sẽ bất ngờ đấy.

Quelle: dev.to (dev.to). Original öffnen