Series 1 - Bài 5: System Prompt Engineering cho Coding Agent

System prompt là code, không phải text. Nó định nghĩa toàn bộ behavior, constraints và personality của coding agent. Bài này phân tích anatomy của một system prompt hiệu quả và các nguyên tắc thiết kế.

Nghe bài viết này dưới dạng podcast

System prompt là thứ dễ bị underinvest nhất trong agent development. Developers thường viết vài dòng mô tả role, test nhanh, thấy OK, và move on. Nhưng system prompt chất lượng thấp là nguyên nhân của phần lớn behavior bất ngờ trong agent system.

System prompt không phải text bạn viết một lần. Nó là code cần version control, cần review, và cần test.

Anatomy của một System Prompt cho Coding Agent

Một system prompt hiệu quả có 5 sections với thứ tự cụ thể:

┌──────────────────────────────────────────────────────────────┐
│ 1. IDENTITY & ROLE          (LLM biết nó là ai)             │
│    ─────────────────────────────────────────────────────    │
│ 2. CONTEXT AWARENESS        (LLM biết nó đang ở đâu)       │
│    ─────────────────────────────────────────────────────    │
│ 3. BEHAVIORAL CONSTRAINTS   (LLM biết không được làm gì)   │
│    ─────────────────────────────────────────────────────    │
│ 4. TOOL USAGE GUIDELINES    (LLM biết dùng tool như thế nào)│
│    ─────────────────────────────────────────────────────    │
│ 5. OUTPUT FORMAT            (LLM biết trả lời thế nào)     │
└──────────────────────────────────────────────────────────────┘

Thứ tự này không ngẫu nhiên. LLM đọc từ trên xuống, và các section sau build trên nền tảng của section trước.

Section 1: Identity & Role

LLM cần biết rõ nó đóng vai gì. Nhưng không phải mọi role description đều như nhau:

Weak role description:
"You are a helpful coding assistant."

Problem: Quá generic. LLM không biết:
- Hệ sinh thái nào?
- Level nào (explain từng dòng hay assume expert)?
- Ưu tiên gì khi có trade-off?

Strong role description:
"You are a senior TypeScript engineer working on a
NestJS + TypeORM backend codebase. You work with
experienced developers — no need to explain basics.
When making changes, prioritize correctness over
completeness: a correct partial solution is better
than an incorrect complete one."

LLM bây giờ biết:
- Stack cụ thể
- Audience level
- Priority khi trade-off

Section 2: Context Awareness

Section này inject dynamic context — thông tin về project cụ thể mà agent đang làm việc:

## Project Context

Project: {{project_name}}
Root: {{working_directory}}
Language: TypeScript 5.x
Framework: NestJS 10, TypeORM 0.3
Test runner: Jest

## File Structure
{{file_tree}}

## Coding Conventions
- Tên file: kebab-case (user-service.ts)
- Tên class: PascalCase
- Inject dependencies qua constructor, không dùng property injection
- Tất cả database operations phải có error handling
- Không commit TODO comments

Phần này là nơi context injection từ Context Layer được đưa vào. File tree và conventions giúp LLM make better decisions mà không cần phải read toàn bộ codebase trước.

Section 3: Behavioral Constraints

Đây là section quan trọng nhất — và thường bị bỏ qua nhất. Constraints định nghĩa những gì agent không được làm:

## Constraints

### Scope
- Chỉ modify files liên quan trực tiếp đến task
- Không refactor code ngoài scope của task (dù muốn)
- Không add dependencies mới mà không thông báo trước

### Safety
- Không delete files
- Không modify files ngoài working directory
- Không chạy commands có side effects lớn (rm, DROP TABLE)
  mà không confirm với user

### Quality
- Luôn run tests sau khi modify code
- Nếu tests fail, fix trước khi báo cáo hoàn thành
- Không báo cáo hoàn thành khi còn TypeScript errors

### Uncertainty
- Nếu task ambiguous, hỏi clarification trước khi làm
- Nếu gặp unexpected complexity, báo cáo và estimate
  effort thực tế trước khi tiếp tục
- Nếu solution có significant trade-off, trình bày
  options và để user quyết định

Constraints có một property quan trọng: chúng phải testable. "Be careful" không phải constraint — "Không delete file" là constraint.

Section 4: Tool Usage Guidelines

LLM cần guidance về khi nào và làm thế nào để dùng tools:

## Tool Usage

### When to use read_file
- Trước khi modify bất kỳ file nào, luôn đọc nó trước
- Không assume về nội dung dựa trên tên file
- Đọc test file trước khi modify implementation

### When to use search_files
- Dùng để tìm references trước khi rename
- Dùng để check nếu có duplicate implementation
- Dùng khi không biết chính xác file nào chứa code cần tìm

### When to use run_command
- Dùng để run tests: npm test, npm run test:unit
- Dùng để check TypeScript: npx tsc --noEmit
- Không dùng cho commands không liên quan đến task

### Tool call ordering
- Đọc trước khi viết
- Hiểu trước khi thay đổi
- Kiểm tra sau khi thay đổi

Guidelines này ảnh hưởng đến số lượng và chất lượng tool calls. LLM không có guidelines thường call tools theo thứ tự không optimal, tốn token không cần thiết.

Section 5: Output Format

## Response Format

### Khi hoàn thành task:
1. Mô tả ngắn gọn những gì đã thay đổi
2. Liệt kê files đã modify
3. Kết quả test run
4. Bất kỳ caveat hoặc follow-up nào cần chú ý

### Khi cần clarification:
- Hỏi một câu hỏi cụ thể, không phải nhiều câu cùng lúc
- Giải thích tại sao cần thông tin đó

### Khi task quá lớn:
- Estimate scope
- Propose breakdown thành smaller tasks
- Confirm với user trước khi proceed

Format guidance giúp output predictable và actionable.

System Prompt là code: Implications

Version Control

Sai: System prompt hardcode trong source
Đúng: System prompt trong file riêng, version controlled

/config
  agent.md          ← system prompt
  agent.v2.md       ← thử nghiệm version mới
  CHANGELOG.md      ← ghi lại thay đổi và lý do

Testing

System prompt có thể và nên được test:

Test cases cho system prompt:

1. Agent có hỏi clarification khi task ambiguous?
2. Agent có confirm trước khi delete file?
3. Agent có run tests sau khi modify code?
4. Agent có scope creep ra ngoài task?
5. Agent có giải thích decision khi có trade-off?

Token Budget

System prompt dài hơn = tốn nhiều token hơn trong mỗi request. Với agent chạy 20 turns và system prompt 2000 tokens, đó là 40,000 tokens chỉ cho system prompt.

System prompt optimization:

Verbose version:
"When you are about to use the write_file tool to
modify a file, you should first make sure that you
have read the current contents of the file using
the read_file tool, so that you understand what
you are changing."

Concise version:
"Always read before write."

Meaning tương đương, 90% ít token hơn.

Nhưng đừng over-optimize đến mức mất clarity. Balance giữa token efficiency và clarity là judgment call.

Trade-off: Long vs Short System Prompt

Long, detailed system prompt:
  + LLM có nhiều guidance hơn
  + Ít ambiguous behavior hơn
  + Dễ predict behavior trong edge cases
  - Token cost cao hơn
  - LLM có thể "lose focus" ở phần cuối prompt dài
  - Maintenance overhead lớn hơn

Short, high-level system prompt:
  + Token efficient
  + LLM có flexibility để adapt
  - Behavior khó predict hơn
  - Edge cases có thể handle không nhất quán
  - Cần nhiều iterative refinement hơn

Không có đáp án đúng. Cần benchmark với use case cụ thể.

Kết luận

  • System prompt có 5 sections theo thứ tự: Identity, Context, Constraints, Tool Guidelines, Output Format.
  • Identity cần cụ thể về stack, audience level, và priority khi trade-off.
  • Constraints phải testable — không phải aspirational statements.
  • Tool usage guidelines giảm suboptimal tool call patterns.
  • System prompt là code: cần version control, cần testing, cần optimization.
  • Balance token efficiency với clarity — không over-optimize đến mức mất meaning.

Chưa có bình luận

Để lại bình luận

Bình luận sẽ được phê duyệt trước khi hiển thị.

Nhận bài viết mới

Mình sẽ gửi email khi có bài mới. Không spam.