Chuyện bắt đầu từ một buổi sáng thứ Hai. Sếp nhắn trên Slack: "Setup AI coding assistant cho team đi, dùng Claude Code, nhưng phải đi qua Bedrock nhé — security team không cho dùng API key cá nhân."
Nghe thì đơn giản. Nhưng khi mình bắt tay vào, mới thấy có kha khá thứ cần hiểu — từ cách Bedrock route requests, cơ chế cache tiết kiệm 90% chi phí, đến chuyện tại sao 1 session dài lại rẻ hơn nhiều session ngắn. Bài này mình kể lại hành trình 5 ngày đó, đi sâu vào phần kỹ thuật để các bạn không phải mò lại từ đầu.
Ngày đầu tiên: Tại sao phải đi qua Bedrock?
Trước khi động vào terminal, mình cần hiểu tại sao không gọi thẳng Anthropic API cho nhanh.
Lý do nằm ở đây: khi dùng API key cá nhân, code công ty đi thẳng ra internet đến server Anthropic. Không ai kiểm soát được ai dùng bao nhiêu, không có audit log, không có budget alert. Với team 5-10 người, mỗi người tự quản lý API key riêng — đó là chaos.
Bedrock giải quyết bằng cách đưa mọi thứ vào AWS infrastructure:
Không có Bedrock:
Developer → API Key → Anthropic Server (internet)
❌ Không audit | ❌ Không budget control | ❌ Mỗi người tự quản lý
Có Bedrock:
Developer → IAM Auth → AWS Bedrock → Claude Model
✅ CloudTrail logs | ✅ Budget Alerts | ✅ IAM quản lý tập trung
Bedrock biến Claude Code từ "tool cá nhân" thành "tool enterprise-ready". Ba lợi ích kỹ thuật cụ thể:
Data isolation: Request không rời khỏi AWS account của tổ chức. Anthropic không dùng data gửi qua Bedrock để train model — điều này được ghi rõ trong AWS Data Processing Addendum. Với team làm việc trên code proprietary hoặc data nhạy cảm, đây là điểm khác biệt quan trọng so với Anthropic API trực tiếp.
CloudTrail audit: Mọi request được log với đầy đủ metadata: model ID, timestamp, token count (input/output/cache), latency, IAM user/role nào gọi. Có thể query Cost Explorer filter theo service Bedrock để xem từng developer dùng bao nhiêu token mỗi ngày — không cần build thêm gì.
Centralized access control: IAM policy quyết định ai được invoke model nào. Thêm/bỏ quyền 1 người chỉ cần thay đổi IAM, không cần thu hồi/rotate API key.
Ngày thứ hai: Setup — nhanh hơn mình tưởng
Mình dự tính mất cả ngày, nhưng thực tế chỉ khoảng 15 phút.
Tip: Claude Code hiện có wizard setup sẵn. Chạyclaude→ chọn "3rd-party platform" → "Amazon Bedrock" → wizard tự detect region, verify model access, và pin version. Hoặc gõ/setup-bedrockbất cứ lúc nào để mở lại. Nếu muốn hiểu từng bước hoặc deploy cho cả team, làm thủ công theo hướng dẫn bên dưới.
Bước đầu tiên — kích hoạt model trên Bedrock. Vào AWS Console → Bedrock → Playgrounds → Chat → chọn Claude Sonnet → gửi 1 message. Lần đầu dùng Anthropic models sẽ có popup yêu cầu điền use case form, submit xong là access ngay. Không cần chờ approve.
Bước hai — tạo IAM Policy. Claude Code cần 5 actions:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream",
"bedrock:ListInferenceProfiles",
"bedrock:ListFoundationModels",
"bedrock:GetInferenceProfile"
],
"Resource": [
"arn:aws:bedrock:*:*:inference-profile/*",
"arn:aws:bedrock:*:*:application-inference-profile/*",
"arn:aws:bedrock:*:*:foundation-model/*"
]
}]
}
InvokeModelWithResponseStream cần thiết vì Claude Code dùng streaming — hiển thị output từng phần thay vì chờ toàn bộ. GetInferenceProfile là action mới: thiếu nó Claude Code vẫn chạy được nhưng mỗi request mới sẽ phải retry thêm 1 round-trip để resolve model shape.
Bước ba — cài Claude Code và trỏ về Bedrock:
npm install -g @anthropic-ai/claude-code
# Thêm vào ~/.zshrc
export CLAUDE_CODE_USE_BEDROCK=1
export AWS_REGION=ap-northeast-1
export ANTHROPIC_MODEL='global.anthropic.claude-sonnet-4-6'
export ANTHROPIC_DEFAULT_HAIKU_MODEL='global.anthropic.claude-haiku-4-5-20251001-v1:0'
Chạy claude, hỏi "what model are you using?" — thấy "Amazon Bedrock" là thành công.
Cái bẫy đầu tiên: global. prefix
Đây là chỗ mình mất 30 phút debug. Mình set ANTHROPIC_MODEL='anthropic.claude-sonnet-4-6' (không có prefix global.) và nhận lỗi "Model not available".
Lý do nằm ở cơ chế Inference Routing của Bedrock. Có 3 loại:
┌─────────────────────────────────────────────────────────────┐
│ Bedrock Inference Routing │
│ │
│ In-Region (không prefix) │
│ → Request xử lý trong đúng region đó │
│ → Nhiều regions KHÔNG có (bao gồm Tokyo) │
│ │
│ Geo Cross-Region (us.* / eu.* / jp.*) │
│ → Route trong 1 geography │
│ → jp.* route trong châu Á, thường cao hơn global.* một chút │
│ │
│ Global Cross-Region (global.*) │
│ → Route đến bất kỳ region nào có capacity │
│ → Throughput cao nhất, giá tốt nhất │
└─────────────────────────────────────────────────────────────┘
Tokyo (ap-northeast-1) không có In-Region inference cho Claude. Phải dùng global. prefix. Bỏ prefix = lỗi ngay.
VS Code Extension — cái bẫy thứ hai
Sau khi CLI chạy OK, mình cài VS Code Extension (anthropic.claude-code). Mở VS Code từ Dock... và nó không nhận Bedrock config.
Lý do: VS Code mở từ Dock/Spotlight không load ~/.zshrc, nên environment variables không có tác dụng. Phải thêm config vào ~/.claude/settings.json:
{
"env": {
"CLAUDE_CODE_USE_BEDROCK": "1",
"AWS_REGION": "ap-northeast-1",
"ANTHROPIC_MODEL": "global.anthropic.claude-sonnet-4-6",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "global.anthropic.claude-haiku-4-5-20251001-v1:0"
}
}
File này được cả CLI lẫn Extension đọc chung. Restart VS Code, xong.
Cái bẫy thứ ba: ANTHROPIC_API_KEY cũ còn sót
Cái này sneaky nhất vì mọi thứ vẫn chạy bình thường — chỉ là tiền đổ vào chỗ khác.
Nếu trước đây đã dùng Claude Code với Anthropic API trực tiếp, máy có thể còn ANTHROPIC_API_KEY trong shell. Claude Code ưu tiên API key nếu có, tức là request đi thẳng ra Anthropic server, không qua Bedrock, không có IAM log, và tiền tính vào key cá nhân.
# Kiểm tra
echo $ANTHROPIC_API_KEY
# Nếu có output → unset ngay
unset ANTHROPIC_API_KEY
# Và xóa luôn trong ~/.zshrc nếu có export ở đó
Một điểm cần lưu ý nếu dùng AWS SSO
Với team dùng IAM Identity Center (SSO), token chỉ sống 8-12 tiếng. Đang code ngon lành tự nhiên thấy lỗi InvalidClientTokenId là SSO hết hạn. Có 2 cách xử lý:
Thủ công — chạy lại khi thấy lỗi:
aws sso login --profile your-profile
Tự động — cấu hình awsAuthRefresh trong ~/.claude/settings.json để Claude Code tự refresh khi credentials hết hạn:
{
"awsAuthRefresh": "aws sso login --profile your-profile",
"env": {
"AWS_PROFILE": "your-profile"
}
}
Với config này, Claude Code tự gọi lệnh refresh khi phát hiện credentials expired, không cần nhớ chạy tay nữa.
Ngày thứ ba: Hiểu cách tính tiền
Đây là phần mình dành nhiều thời gian nhất, vì nó ảnh hưởng trực tiếp đến budget team.
Bedrock tính phí theo token — mỗi ~4 ký tự tiếng Anh = 1 token, tiếng Việt/Nhật thì 1-2 ký tự. Bảng giá tham khảo (per 1 triệu tokens):
| Model | Input | Output | Tỷ lệ Output/Input |
|---|---|---|---|
| Haiku 4.5 | $0.80 | $4.00 | 5x |
| Sonnet 4.6 | $3.00 | $15.00 | 5x |
| Opus 4.7 | $15.00 | $75.00 | 5x |
Lưu ý: Đây là giá Anthropic API để tham khảo tỷ lệ. AWS Bedrock chưa công bố giá chính thức cho Claude 4 trên trang pricing. Giá thực trên Bedrock có thể khác — kiểm tra aws.amazon.com/bedrock/pricing trước khi estimate budget.
Điểm quan trọng nhất: output luôn đắt gấp 5 lần input. Prompt càng cụ thể → output càng ngắn → tiền càng ít.
Prompt Caching — thứ tiết kiệm 90% mà mình gần bỏ qua
Buổi chiều ngày thứ ba, mình check /cost và thấy "cache read" chiếm hơn nửa chi phí. Tò mò đào sâu vào cơ chế này.
Nó hoạt động thế này: mỗi request gửi đến Bedrock bao gồm một context prefix — system prompt, project files, conversation history. Lần đầu, Bedrock phải xử lý toàn bộ prefix này (gọi là Cache Write, tốn 1.25x giá input). Từ lần 2 trở đi, nếu prefix không đổi, Bedrock đọc lại từ cache (Cache Read, chỉ 0.1x giá input).
Session bắt đầu
│
▼
Câu 1: Nạp context → CACHE WRITE
│ Cost: 1.25x input price
│ TTL: 5 phút bắt đầu đếm
│
▼ (hỏi tiếp trong 5 phút)
Câu 2: Context giống → CACHE READ
│ Cost: 0.1x input price (rẻ hơn 12.5 lần!)
│ TTL reset về 5 phút
│
▼
Câu 3, 4, 5...: Tiếp tục Cache Read
│ Chi phí ổn định, rất rẻ
│
▼ (nghỉ > 5 phút không hỏi gì)
Cache hết hạn → phải Cache Write lại từ đầu
Điều kiện cache hit (tất cả phải thỏa):
- Cùng session — restart Claude Code = mất cache
- Context prefix không đổi — thêm/xóa file = invalidate cache
- Trong TTL window — mỗi lần read tự động refresh TTL
Break-even: khi nào caching có lợi?
Cache Write tốn 1.25× giá input — overhead +25%. Nhưng từ request thứ 2 trở đi, mỗi lần chỉ tốn 0.1× giá input. Tổng chi phí phần context cho session N câu hỏi (C = context size, P = input price):
Không cache: N × C × P
Có cache: C×P×1.25 + (N-1)×C×P×0.1 = C×P × (1.25 + 0.1(N-1))
N=2: 1.35 vs 2.00 → tiết kiệm 32%
N=5: 1.65 vs 5.00 → tiết kiệm 67%
N=10: 2.15 vs 10.00 → tiết kiệm 78%
Ngay từ câu hỏi thứ 2, caching đã có lợi. Session càng dài, context prefix càng lớn (nhiều file), lợi càng rõ — vì overhead Cache Write là fixed cost, còn savings tích lũy mỗi request.
Mặc định TTL là 5 phút. Nếu hay bị ngắt giữa chừng, có thể bật TTL 1 tiếng (tính phí cao hơn một chút):
export ENABLE_PROMPT_CACHING_1H=1
Mình test thực tế một session 17 phút với Sonnet:
Total: $1.00
Cache Read: 1,800,000 tokens → $0.54 (54%) ← phần lớn!
Output: 16,700 tokens → $0.25 (25%)
Cache Write: 54,800 tokens → $0.21 (21%) ← "phí khởi tạo"
Input: 43 tokens → $0.00 (0%)
Nếu không có caching, 1.8M tokens đó sẽ tốn $5.40 thay vì $0.54. Tiết kiệm 90% — không phải con số marketing.
Bài học lớn nhất: 1 session 2 giờ rẻ hơn rất nhiều so với 4 session 30 phút. Mỗi session mới = Cache Write lại toàn bộ.
Ngày thứ tư: Chọn model — không phải cứ đắt là tốt
Sau khi hiểu cách tính phí, mình bắt đầu thử 3 models.
| Spec | Haiku 4.5 | Sonnet 4.6 | Opus 4.7 |
|---|---|---|---|
| Context | 200K tokens | 1M tokens | 1M tokens |
| Max output | 64K | 64K | 128K |
| Cost (input/output per 1M) | $0.80/$4 | $3/$15 | $15/$75 |
| Tốt nhất cho | Search, explain, boilerplate | Daily coding, feature dev, tests | Complex reasoning, architecture |
| Bắt đầu struggle khi | Multi-file refactor, complex logic | Subtle bugs, deep optimization | — |
| Latency | Thấp nhất | Trung bình | Cao nhất |
Một điểm hay: Sonnet 4.6 cùng giá với Sonnet 4.5 nhưng context tăng từ 200K lên 1M, chất lượng cải thiện rõ rệt. Không có lý do dùng Sonnet 4.5 nữa.
Sau vài ngày thử, mình đúc kết ra pattern:
- Haiku — tìm kiếm file, giải thích đoạn code, review 1-2 files, generate boilerplate. Bắt đầu "struggle" khi task yêu cầu hiểu dependency qua nhiều files hoặc refactor logic phức tạp — output nhìn đúng nhưng miss cross-file context.
- Sonnet — daily coding, tạo feature, viết tests, refactor vừa phải, debug hầu hết bugs. Context 1M tokens đủ load cả codebase 100K dòng. Giới hạn ở bugs cực kỳ subtle (race conditions ẩn, off-by-one trong algorithm phức tạp) hoặc khi cần architectural reasoning dài hơi.
- Opus — debug performance bottleneck khó, thiết kế distributed system, phân tích security vulnerability, review architectural decision nhiều trade-off. Đắt ~5x Sonnet. Mình dùng 1-2 lần/ngày cho những task thực sự cần deep reasoning, không phải default.
Claude Code cho phép đổi model giữa session mà không mất cache:
/model haiku # task nhẹ
/model sonnet # daily coding
/model opus # task phức tạp
Với Opus tốn kém, pattern thực tế là: dùng /model opus chỉ cho bước lên kế hoạch hoặc debug phức tạp, sau đó /model sonnet để code. Không cần Opus đứng đó cả session.
Ngày thứ năm: Multi-agent — lúc mọi thứ thay đổi
Đây là ngày mình thấy "wow" thật sự. Claude Code cho phép tạo subagents — AI assistant chuyên biệt, mỗi agent có context riêng, tools riêng, model riêng.
Tại sao cần? Single agent với task phức tạp có 1 vấn đề cố hữu: context bị "ô nhiễm" dần. Output verbose từ bước review lẫn vào context của bước code, chuyển qua lại giữa các task khiến chất lượng giảm, và conversation dài thì model bắt đầu "quên" thông tin từ đầu session.
Subagent giải quyết bằng cách cho mỗi task một context window riêng biệt, không bị ô nhiễm bởi task khác:
┌──────────────────────────────────────────────────────────┐
│ Main Agent (Orchestrator) │
│ Model: Sonnet | Context: 1M tokens │
│ Nhận request → Delegate → Tổng hợp │
├──────────┬──────────────┬──────────────┬─────────────────┤
│ │ │ │ │
│ ┌──────▼──────┐ ┌─────▼──────┐ ┌─────▼───────┐ │
│ │ Explore │ │ Reviewer │ │ Test │ │
│ │ (Haiku) │ │ (Haiku) │ │ Generator │ │
│ │ │ │ │ │ (Sonnet) │ │
│ │ Read-only │ │ Read-only │ │ Read+Write │ │
│ │ 200K ctx │ │ 200K ctx │ │ 1M ctx │ │
│ └─────────────┘ └────────────┘ └─────────────┘ │
│ │
│ ⚠️ Subagent KHÔNG THỂ spawn subagent (no nesting) │
└──────────────────────────────────────────────────────────┘
Tạo custom agent
Chỉ cần 1 file markdown trong .claude/agents/:
---
name: code-reviewer
description: Reviews code for quality, security, and best practices
model: haiku
tools: [Read, Grep, Glob]
---
You are a senior code reviewer. Check for:
- Code quality and readability
- Security issues (hardcoded secrets, SQL injection, XSS)
- Error handling completeness
Format: ✅ Good | ⚠️ Warning | ❌ Critical
Trường description quan trọng hơn mình nghĩ ban đầu — đây là cơ chế routing. Claude đọc description để quyết định có delegate task này không. Viết tệ thì agent không bao giờ được gọi:
# ❌ Quá vague — Claude ít khi delegate
description: Reviews code
# ✅ Rõ routing condition
description: Use when the user asks to review, check, audit, or inspect
existing code for quality, bugs, or security issues. Do NOT use for
writing new code or adding features.
Description tốt cần 2 phần: (1) khi nào dùng và (2) khi nào không dùng. Thiếu phần (2) dễ dẫn đến agent bị trigger sai — reviewer agent đi viết code mới thì không ai muốn. Nếu cần trigger explicit thay vì auto-delegate:
> Use the code-reviewer agent to review src/app.js
Cơ chế tool permission ở đây quan trọng — đây là least privilege cho AI:
Read-only agent (reviewer): tools: [Read, Grep, Glob]
Agent sửa code (debugger): tools: [Read, Grep, Glob, Edit]
Agent tạo file (test writer): tools: [Read, Grep, Glob, Write]
Full access (cẩn thận): tools: [Read, Grep, Glob, Edit, Write, Bash]
Agent review chỉ được đọc, không sửa được code — an toàn hơn nhiều.
Foreground vs Background
Subagent có 2 mode chạy:
- Foreground (default): main agent dừng chờ kết quả. Dùng khi bước tiếp phụ thuộc vào output.
- Background: chạy song song, main agent tiếp tục làm việc khác. Nói "run this in the background" là Claude Code tự xử lý.
Benchmark — con số nói lên tất cả
Theo nghiên cứu của Anthropic (xem "Building Effective Agents" trong References):
| Metric | Single Agent | Multi-Agent | Cải thiện |
|---|---|---|---|
| Task completion | 47.3% | 90.2% | +90% |
| Code quality | 65% | 89% | +37% |
| Token usage | ~8K | ~120K | +15x |
Token tăng 15x nhưng task completion gần gấp đôi. Và nhờ Prompt Caching, phần lớn tokens tăng thêm là cache read (rẻ), nên chi phí thực tế không tăng tuyến tính.
Demo: Một session thực tế
Mình demo flow làm việc thực tế để các bạn hình dung:
# Mở Claude Code
$ claude
# Yêu cầu tạo API
> Tạo Express API: GET/POST/DELETE /users, validation, error handling, tests
# Claude Code tự động tạo 4 files, 287 dòng
# Check chi phí
> /cost
Session cost: $0.47 | 8 phút
# Delegate review cho subagent
> Delegate to code-reviewer to review all files
⏺ code-reviewer (Haiku)
✅ Error handling: try-catch đầy đủ
⚠️ Security: thiếu rate limiting
❌ No helmet middleware
⎿ Done (8.1s, +$0.15)
# Đổi model cho task nhẹ
> /model haiku
> Thêm helmet middleware → Done (+$0.02)
# Đổi lại Sonnet cho refactor
> /model sonnet
> Refactor error handling thành middleware riêng → Done (+$0.25)
# Tổng session
> /cost
Total: $0.89 | 22 phút | 6 files | 342 lines
Sonnet: $0.58 (coding + refactor)
Haiku: $0.31 (simple tasks + review)
22 phút, 6 files, có cả review và refactor, chưa đến $1.
Sau 1 tuần: Chi phí thực tế và tips
Ước tính hàng tháng (22 ngày)
| Mức độ | Mô tả | Sonnet only | Tối ưu (60% Haiku + 40% Sonnet) |
|---|---|---|---|
| Nhẹ | Review, sửa nhỏ | ~$50 | ~$30 |
| Trung bình | Feature dev, tests | ~$150 | ~$90 |
| Nặng | Full agentic, refactoring | ~$300 | ~$180 |
Tips đúc kết sau 1 tuần sử dụng
Session dài = rẻ. Tip quan trọng nhất. Mở Claude Code, hỏi 15-20 câu liên tục rồi tắt. Đừng hỏi 1 câu, tắt, mở lại — mỗi lần mở là Cache Write lại.
Batch tasks tương tự. Review 3 files trong 1 session: 1 Cache Write + 2 Cache Read, rẻ hơn nhiều so với 3 session riêng.
Prompt cụ thể. "Review main.py, liệt kê bugs và fix" thay vì "xem qua file này giúp tôi". Output đắt 5x input, prompt cụ thể giúp output ngắn hơn.
.claudeignore để loại các thư mục không cần thiết. Context nhỏ hơn = Cache Write ít hơn = rẻ hơn. Tạo file .claudeignore ở root project:
node_modules/
dist/
build/
.git/
coverage/
*.lock
*.log
*.min.js
*.map
Syntax giống .gitignore. Claude Code đọc file này và bỏ qua hoàn toàn những path đó khi nạp context.
/compact khi conversation dài. Nén context, tiết kiệm token cho câu hỏi sau.
Budget Alert — setup ngay từ đầu, đừng đợi cuối tháng mới biết:
aws budgets create-budget \
--account-id $(aws sts get-caller-identity --query Account --output text) \
--budget '{
"BudgetName": "Bedrock-Monthly",
"BudgetLimit": {"Amount": "100", "Unit": "USD"},
"BudgetType": "COST",
"TimeUnit": "MONTHLY",
"CostFilters": {"Service": ["Amazon Bedrock"]}
}'
Nhìn lại 1 tuần
Setup mất 15 phút. Hiểu cách tính phí mất 1 ngày. Tối ưu multi-agent mất thêm 1 ngày nữa. Mấy đứa trong team ban đầu hơi skeptical — "AI mà setup nhiều vậy?" — nhưng sau khi thử 1 buổi thì không ai hỏi thêm nữa.
Nếu chỉ nhớ 1 thứ từ bài này: đừng mở nhiều session ngắn. Cache Write là khoản "phí vào cửa" — trả 1 lần rồi hỏi thả ga. Phần còn lại, chọn model và tạo agent, là tối ưu thêm, không phải điều kiện bắt buộc.
Chi phí thực tế với mix 60/40 Haiku/Sonnet khoảng $90/tháng/người. Nếu mỗi ngày tiết kiệm được 30 phút code review thì ROI tính trong vài tuần — và đó là ước tính thận trọng.
