Bài này bạn sẽ dựng một Documentation Agent "chạy được" ở mức tối thiểu: đọc một file code, gọi LLM, và trả về docstring. Từ khung này ta mở rộng dần ở các bài sau.
Kiến trúc thành phần
Một Documentation Agent gồm 4 khối:
- Source Reader — đọc code (file, diff, hoặc AST).
- LLM Client — gọi model để sinh/chỉnh docs.
- Prompt Layer — chứa system prompt + template cho từng loại docs.
- Writer — ghi kết quả ra file/PR/comment.
Source Reader --> Prompt Layer --> LLM Client --> Writer
Các bước thiết lập
- Tạo virtualenv và cài thư viện:
python -m venv .venv
source .venv/bin/activate
pip install openai anthropic python-dotenv
- Đặt API key vào
.env(không commit file này):
OPENAI_API_KEY=sk-...
MODEL=gpt-4o-mini
- Viết LLM Client mỏng — tách khỏi logic để dễ đổi provider:
import os
from openai import OpenAI
from dotenv import load_dotenv
load_dotenv()
client = OpenAI()
def ask_llm(system, user):
resp = client.chat.completions.create(
model=os.getenv("MODEL", "gpt-4o-mini"),
temperature=0.2, # docs can chinh xac, khong sang tao
messages=[
{"role": "system", "content": system},
{"role": "user", "content": user},
],
)
return resp.choices[0].message.content
- Tạo system prompt chuyên biệt cho tài liệu:
DOC_SYSTEM = (
"Ban la ky su tai lieu. CHI mo ta nhung gi CO trong code. "
"Neu khong chac, ghi 'khong ro tu code'. Khong bia tham so, "
"khong bia gia tri tra ve. Giu dung ngon ngu goc cua project."
)
- Ghép thành agent tối thiểu sinh docstring cho một file:
def gen_docstring(path):
code = open(path).read()
user = f"Sinh docstring kieu Google cho cac ham trong file:\n\n{code}"
return ask_llm(DOC_SYSTEM, user)
if __name__ == "__main__":
import sys
print(gen_docstring(sys.argv[1]))
- Chạy thử:
python agent.py yourfile.pyvà đọc kết quả.
Vì sao temperature thấp
Tài liệu cần chính xác và ổn định, không cần sáng tạo. Đặt temperature=0.2 (hoặc thấp hơn) giúp giảm bịa đặt (hallucination) và làm output ổn định giữa các lần chạy — quan trọng khi bạn muốn diff kết quả.
Khung tư duy: chống hallucination ngay từ prompt
Rủi ro số một của doc agent là bịa tham số, kiểu trả về, hành vi không tồn tại. Ba lớp phòng thủ:
- Prompt ràng buộc — yêu cầu "chỉ mô tả cái có trong code", cho phép nói "không rõ".
- Ngữ cảnh đủ — đưa nguyên hàm + chữ ký + type hint, không đưa mẩu cụt.
- Verify sau sinh — đối chiếu tham số trong docs với chữ ký thật (Bài 6).
Vì sao tách LLM Client thành một lớp mỏng
Đừng gọi API của provider rải rác trong toàn bộ mã. Hãy gói mọi lời gọi qua một hàm ask_llm() duy nhất. Lợi ích rất thực tế: khi bạn muốn đổi từ OpenAI sang Anthropic, sang một model chạy local (như qua Ollama) để tiết kiệm chi phí, hoặc thêm retry/logging/đếm token, bạn chỉ sửa một chỗ. Lớp mỏng này cũng là nơi lý tưởng để chèn cache (Bài 7): trước khi gọi model, kiểm tra xem nội dung hàm này đã sinh docs chưa, nếu chưa đổi thì trả về kết quả cũ, khỏi tốn tiền.
Cấu trúc thư mục gợi ý
Để agent dễ mở rộng qua các bài, tổ chức mã ngay từ đầu:
doc_agent/
__init__.py
llm.py # ask_llm(), cau hinh provider
prompts.py # DOC_SYSTEM va cac template
reader.py # doc code (Bai 3)
generator.py # sinh docstring/README (Bai 4-5)
verifier.py # phat hien drift (Bai 6)
cli.py # entrypoint check|fix (Bai 7)
Mỗi khối một file, ghép qua CLI. Kiến trúc này giúp bạn thay từng phần (ví dụ đổi reader Python sang tree-sitter) mà không đụng phần còn lại.
Kiểm thử nhanh khung tối thiểu
- Tạo một file
sample.pyvới 2-3 hàm đơn giản, cố tình bỏ trống docstring. - Chạy
python -m doc_agent.cli sample.py. - Đọc output: mọi tham số có được mô tả đúng không? Có tham số bịa không?
- Thử hạ
temperaturexuống 0 và chạy lại 3 lần — output có ổn định (giống nhau) không? - Ghi lại thời gian và số token — đây là baseline chi phí bạn sẽ tối ưu ở Bài 7.
Sai lầm thường gặp
- Hard-code provider vào khắp nơi. Khi cần đổi từ OpenAI sang model local, bạn phải sửa hàng chục chỗ. Luôn để một
ask_llm()duy nhất. - Để temperature cao. Docs sẽ "bay", mỗi lần chạy một kiểu, khó review bằng diff.
- Nhồi cả repo vào một prompt. Vượt context window, tốn tiền, và model lạc. Ở bài sau ta sẽ đọc theo diff/AST thay vì cả file.
- Commit
.env. Lộ API key. Luôn thêm.envvào.gitignore. - Không xử lý lỗi mạng/timeout. API sẽ có lúc lỗi; bọc
ask_llm()bằng retry có backoff để agent không sập giữa chừng.