CodeAct 2026: Khi AI Agent Viết Code Thay Vì Gọi JSON Tool
Posted on: 5/14/2026 10:31:20 AM
Table of contents
- 1. CodeAct là gì — và vì sao nó khác JSON tool call?
- 2. Lịch sử ngắn của CodeAct
- 3. Kiến trúc CodeAct trong production
- 4. So sánh CodeAct vs JSON Tool Call
- 5. Walkthrough — Build CodeAgent với Smolagents
- 6. Bảo mật — Sandbox là điều kiện sống còn
- 7. Observation Loop và State giữa các turn
- 8. 6 lỗi thường gặp khi triển khai CodeAct
- 9. CodeAct trong hệ thống multi-agent
- 10. Tương lai — Code có thực sự là "universal action space"?
- 11. Kết luận
Năm 2024, hầu hết AI Agent giao tiếp với công cụ thông qua một định dạng quen thuộc: JSON tool calls. Mỗi bước, LLM xuất ra một block JSON gọi đúng một function — sạch, dễ parse, dễ validate. Nhưng đến cuối 2025 và xuyên suốt 2026, một làn sóng mới đang đảo lộn pattern này: CodeAct — agent viết thẳng đoạn code Python (thay vì JSON), chạy trong , dùng kết quả luôn cho bước tiếp theo. Smolagents của Hugging Face mặc định dùng CodeAct. Manus AI gây sốt đầu năm 2026 cũng đặt cược vào "code as the universal action space". Bài nghiên cứu gốc của Wang et al. (ICML 2024) đã đo được mức cải thiện ấn tượng: tăng 20% success rate, giảm 30% số bước. Vì sao một thay đổi tưởng nhỏ về định dạng output lại thay đổi cả ngành?
1. CodeAct là gì — và vì sao nó khác JSON tool call?
CodeAct (viết tắt của Code as Action) là paradigm trong đó mỗi "hành động" của AI Agent không phải là một JSON object gọi một tool đơn lẻ, mà là một đoạn code thực thi được (thường là Python) — trong đó các tool được expose dưới dạng function, và LLM được tự do compose chúng bằng biến số, vòng lặp, điều kiện.
Ví dụ trực quan. Giả sử agent cần "tìm 5 thành phố lớn nhất Việt Nam, lấy dân số mỗi thành phố, rồi tính tổng".
Cách cũ — JSON Tool Call (ReAct pattern)
Bước 1: LLM xuất {"tool": "search_cities", "args": {"country": "VN", "limit": 5}} → runtime chạy → trả lại danh sách 5 thành phố.
Bước 2: LLM xuất {"tool": "get_population", "args": {"city": "Hồ Chí Minh"}} → trả 9.3M.
Bước 3..6: Lặp lại 4 lần nữa cho 4 thành phố còn lại.
Bước 7: LLM tự cộng các số trong context → ra kết quả.
Tổng: 7 bước, 7 LLM call, dễ sai khi cộng số trong đầu.
Cách mới — CodeAct
Bước 1: LLM xuất một block code:
cities = search_cities(country="VN", limit=5)
populations = [get_population(city=c) for c in cities]
total = sum(populations)
print(f"Total: {total:,}")
Sandbox chạy → in ra Total: 24,500,000 → agent đọc và trả lời người dùng. Tổng: 2 bước, 2 LLM call, math do Python xử lý — không sai.
2. Lịch sử ngắn của CodeAct
CodeAgent là class chính, ToolCallingAgent chỉ là alternative. Đây là tín hiệu mainstream đầu tiên.3. Kiến trúc CodeAct trong production
Một hệ thống CodeAct chuẩn gồm 5 thành phần — đáng chú ý là trở thành thành phần bắt buộc, không tuỳ chọn nữa.
graph TB
subgraph User["Người dùng"]
Q["Câu hỏi / Task"]
end
subgraph Agent["Agent Loop"]
LLM["LLM (planner + code writer)"]
PARSE["Code Parser / Validator"]
end
subgraph Sandbox["Sandbox Runtime (CRITICAL)"]
EXE["Python Interpreter (E2B / Pyodide / Docker / Modal)"]
TOOL["Tools as Python functions"]
end
subgraph State["State Management"]
VAR["Variables (persisted across turns)"]
OUT["stdout / stderr"]
end
Q --> LLM
LLM -->|"Code block"| PARSE
PARSE -->|"AST validated"| EXE
EXE <--> TOOL
EXE --> VAR
EXE --> OUT
OUT -->|"Observation"| LLM
VAR -.->|"Reuse next turn"| EXE
classDef user fill:#e94560,stroke:#fff,color:#fff
classDef agent fill:#16213e,stroke:#fff,color:#fff
classDef fill:#ff9800,stroke:#fff,color:#fff
classDef state fill:#f8f9fa,stroke:#e94560,color:#2c3e50
class Q user
class LLM,PARSE agent
class EXE,TOOL
class VAR,OUT state
Hình 1 — Vòng lặp CodeAct: LLM sinh code → thực thi → observation feedback ngược về LLM. Variables được giữ giữa các turn để compose phức tạp.
5 thành phần
- LLM Planner + Coder: Cùng một model vừa lên kế hoạch vừa viết code. Model phải đủ giỏi Python (Claude Sonnet, GPT-4 class trở lên) — đây là lý do CodeAct kém hiệu quả với model nhỏ <7B.
- Code Parser/Validator: Trích code block từ output của LLM (thường nằm giữa
```python), kiểm tra AST để chặn import nguy hiểm trước khi gửi vào . - Sandbox Runtime: Bắt buộc. Lựa chọn 2026: E2B (microVM Firecracker), Pyodide+Deno (WebAssembly), Modal, Docker, Daytona, Azure Container Apps Dynamic Sessions.
- Tools as Functions: Tool không phải JSON schema mà là Python function được pre-import vào namespace của . LLM có thể inspect docstring/signature.
- State Persistence: Biến số phải tồn tại giữa các turn (không reset mỗi lần). Sandbox cần stateful — đây là điểm Pyodide thuần khó hơn microVM.
4. So sánh CodeAct vs JSON Tool Call
| Tiêu chí | JSON Tool Call | CodeAct |
|---|---|---|
| Định dạng output | JSON object hợp schema | Đoạn Python (Markdown code block) |
| Số tool/step | 1 | Nhiều (có thể loop, branch) |
| Tính toán phụ | LLM tự cộng/sort trong đầu (dễ sai) | Python xử lý chính xác |
| Xử lý lỗi giữa chừng | Phải quay về LLM mỗi lần fail | try/except ngay trong code, không tốn LLM call |
| Composability | Thấp — mỗi tool độc lập | Cao — output tool A là input tool B trực tiếp |
| Yêu cầu LLM | Bất kỳ model có function calling | Model phải biết Python tốt (≥7B, lý tưởng ≥30B) |
| Bảo mật | Đơn giản — validate JSON là đủ | Phức tạp — bắt buộc isolation |
| Debug | Trace JSON dễ đọc | Cần log code + stdout + variable state |
| Latency mỗi step | Thấp (1 tool call) | Cao hơn ( cold start ~50-200ms) |
| Tổng latency end-to-end | Cao (nhiều round trip LLM) | Thấp (ít round trip hơn) |
Khi nào nên chọn CodeAct?
- Task cần compose nhiều tool trong một bước (data pipeline, ETL nhỏ).
- Cần tính toán/aggregation trên kết quả (sum, sort, group).
- Logic có vòng lặp/điều kiện (xử lý từng item trong list).
- Đã có infrastructure sẵn (E2B, Modal, Daytona...).
Khi nào nên giữ JSON Tool Call?
- Tool đơn giản, một bước (gửi email, tạo Jira ticket).
- Model nhỏ (Llama 3.2 3B, Mistral 7B) — code generation kém.
- Môi trường không thể có (regulated industries, edge devices).
- Cần audit log dễ đọc cho compliance — JSON dễ track hơn code.
5. Walkthrough — Build CodeAgent với Smolagents
Smolagents là implementation chuẩn nhất của CodeAct. Code dưới đây tạo agent biết tự viết Python để hoàn thành task.
from smolagents import CodeAgent, LiteLLMModel, tool
@tool
def search_cities(country: str, limit: int = 5) -> list[str]:
"""Tìm các thành phố lớn nhất theo quốc gia.
Args:
country: Mã ISO quốc gia (VN, US, ...)
limit: Số thành phố trả về
"""
# Giả lập — production gọi API thật
return ["Hồ Chí Minh", "Hà Nội", "Đà Nẵng", "Hải Phòng", "Cần Thơ"][:limit]
@tool
def get_population(city: str) -> int:
"""Lấy dân số một thành phố."""
data = {"Hồ Chí Minh": 9300000, "Hà Nội": 8500000,
"Đà Nẵng": 1230000, "Hải Phòng": 2050000, "Cần Thơ": 1240000}
return data.get(city, 0)
model = LiteLLMModel(model_id="anthropic/claude-sonnet-4-6")
agent = CodeAgent(
tools=[search_cities, get_population],
model=model,
executor_type="e2b", # : e2b | docker | local
additional_authorized_imports=["statistics"],
max_steps=5,
)
result = agent.run("Tổng dân số 5 thành phố lớn nhất Việt Nam là bao nhiêu? Tính cả độ lệch chuẩn.")
print(result)
Khi chạy, agent sẽ tự sinh code tương tự:
cities = search_cities(country="VN", limit=5)
populations = [get_population(city=c) for c in cities]
import statistics
total = sum(populations)
stdev = statistics.stdev(populations)
print(f"Tổng: {total:,}")
print(f"Độ lệch chuẩn: {stdev:,.0f}")
Một LLM call duy nhất hoàn thành cả việc tìm thành phố, lấy dân số, cộng tổng và tính statistics — điều mà JSON tool call cần ít nhất 7-8 round trip.
6. Bảo mật — Sandbox là điều kiện sống còn
CodeAct chỉ an toàn khi tốt. Nếu cho phép LLM-generated code chạy ngay trên process chính, một prompt injection nhẹ cũng có thể os.system("rm -rf /"). Bảng so sánh các runtime phổ biến 2026:
| Runtime | Cơ chế cô lập | Cold start | Stateful | Phù hợp |
|---|---|---|---|---|
| E2B | Firecracker microVM | ~150ms | Có | Production agent, multi-tenant |
| Modal | gVisor + container | ~500ms | Có | Workload nặng tính toán/GPU |
| Daytona | Container + LXC | ~200ms | Có | Dev environment + agent |
| Azure Container Apps Dynamic Sessions | Hyper-V + Code Interpreter | ~300ms | Có (60 phút) | Enterprise Microsoft stack |
| Pyodide + Deno | WebAssembly + permission flag | ~50ms | Khó (per-call) | Edge, lightweight, single-tenant |
| Docker | Linux namespaces (yếu hơn microVM) | ~1-2s | Có | Dev/POC, không production multi-tenant |
| Local Python (KHÔNG dùng) | Không có | 0ms | Có | Không bao giờ |
5 checklist bảo mật bắt buộc
- Network egress filter: chặn outbound trừ allowlist (tránh data exfiltration).
- Filesystem read-only trừ
/tmp; không mount secret vào . - CPU + memory + wall-clock limit: hạn 30s, 512MB — đủ task hợp lệ, chặn loop vô hạn.
- AST validation trước khi exec: chặn
__import__,exec,eval,open("/etc/passwd"). - Mỗi user một riêng: không bao giờ share giữa các tenant.
7. Observation Loop và State giữa các turn
Điểm tinh tế nhất của CodeAct là vòng lặp multi-turn. Sau mỗi turn, agent cần "thấy" cả 3 thứ: (1) code đã chạy, (2) stdout/stderr, (3) biến số còn sống trong namespace.
sequenceDiagram
participant U as User
participant A as Agent (LLM)
participant S as Sandbox
U->>A: "Phân tích log lỗi tuần này"
A->>S: code: logs = fetch_logs(days=7)
S-->>A: stdout: "(fetched 12,450 records)" + var: logs
Note over A: LLM thấy var logs sẵn sàng
A->>S: code: errors = [l for l in logs if l.level=="ERROR"]
top = Counter(e.module for e in errors).most_common(5)
print(top)
S-->>A: stdout: [("payment", 234), ("auth", 189), ...]
A->>U: "Module payment lỗi nhiều nhất (234 lần)..."
Hình 2 — State (var logs) được giữ giữa các turn, cho phép LLM tham chiếu lại mà không phải re-fetch.
Đây là khác biệt lớn với JSON tool call: ở pattern cũ, mọi observation phải nhồi vào prompt context của turn sau. Với CodeAct, observation lớn (12.450 record log) giữ ở memory, LLM chỉ cần tham chiếu tên biến — tiết kiệm cực nhiều token.
8. 6 lỗi thường gặp khi triển khai CodeAct
Lỗi 1 — Không reset namespace giữa task khác user
Sandbox stateful tiện lợi nhưng nếu user A và user B share container → biến của A leak sang B. Fix: Mỗi conversation/user một instance, hoặc explicit %reset giữa session.
Lỗi 2 — Cho LLM tự import bất kỳ thư viện nào
Nguy cơ supply chain attack (LLM bị prompt injected import malicious package). Fix: additional_authorized_imports allowlist; chặn pip install runtime.
Lỗi 3 — Truncate stdout dài
Agent print() 50.000 dòng → stdout vào prompt → vỡ context window. Fix: Sandbox tự truncate stdout >10KB, gợi ý LLM dùng pagination.
Lỗi 4 — Không phân biệt Final Answer vs Code
LLM đôi khi vừa viết code vừa trả lời cuối cùng trong cùng response. Fix: Smolagents dùng special tool final_answer() — agent phải gọi explicit để kết thúc.
Lỗi 5 — Sandbox cold-start latency cao
Mỗi conversation tạo mới → 2-3s latency đầu tiên. Fix: Sandbox pool warmed-up (E2B, Modal đều hỗ trợ), hoặc Pyodide cho task ngắn.
Lỗi 6 — Bỏ qua observability
Khác JSON, code tự sinh khó debug retroactively. Fix: Log đầy đủ code + stdout + stderr + execution time + variables snapshot vào trace store (Langfuse, LangSmith, hoặc tự build trên ClickHouse).
9. CodeAct trong hệ thống multi-agent
Khi scale sang nhiều agent, CodeAct mở ra pattern thú vị: orchestrator agent gọi sub-agent như function. Smolagents có khái niệm managed_agents — sub-agent được wrap thành callable trong namespace của parent.
from smolagents import CodeAgent
researcher = CodeAgent(tools=[web_search, scrape], model=model, name="researcher",
description="Tìm kiếm và đọc nội dung web")
analyst = CodeAgent(tools=[run_sql, plot_chart], model=model, name="analyst",
description="Phân tích số liệu và vẽ biểu đồ")
orchestrator = CodeAgent(
tools=[],
model=model,
managed_agents=[researcher, analyst],
max_steps=10,
)
orchestrator.run("Tìm 3 đối thủ chính của OpenAI 2026, lấy doanh thu, vẽ biểu đồ so sánh")
Orchestrator có thể viết code kiểu:
competitors_text = researcher(query="3 đối thủ chính của OpenAI 2026 và doanh thu")
chart_url = analyst(query=f"Vẽ bar chart từ dữ liệu: {competitors_text}")
print(chart_url)
Đây là biểu hiện thuần khiết nhất của "agents as functions" — không cần message bus phức tạp, không cần pub/sub, chỉ là Python function call trong .
10. Tương lai — Code có thực sự là "universal action space"?
Apple ML Research (2026) bình luận CodeAct là "the most expressive action format we have today". Nhưng vẫn còn câu hỏi mở:
3 hướng đang được nghiên cứu
- Domain-specific languages: Python tổng quát có cần thiết không, hay DSL hẹp hơn (SQL, Cypher, JAX) đủ và an toàn hơn?
- Type-safe code agents: Liệu LLM viết TypeScript/Rust có giảm bug runtime so với Python?
- Hybrid format: JSON cho tool simple, code cho composition — như cách Anthropic đang làm với
bash+ structured tools song song.
Một điều khá rõ vào 2026: thế hệ agent framework mới đều native code execution (Smolagents, OpenHands, Manus, Letta), trong khi các framework cũ (LangChain, AutoGen) đang nhanh chóng thêm code-mode để bắt kịp. Nếu bạn xây agent system mới hôm nay, CodeAct là default đáng cân nhắc nghiêm túc — chỉ cần đầu tư đúng mức vào .
11. Kết luận
CodeAct không phải "magic" — nó đơn giản là dùng đúng công cụ cho đúng việc. JSON tốt cho RPC, code tốt cho composition. Khi agent ngày càng đảm nhận task phức tạp (data analysis, multi-step reasoning, orchestration), JSON tool call dần lộ rõ giới hạn về số round trip và khả năng compose. CodeAct trả lại cho LLM thứ ngôn ngữ mà chính máy tính được thiết kế để hiểu — code.
Tuy nhiên cái giá phải trả là complexity về . Đừng triển khai CodeAct mà không có chiến lược isolation rõ ràng. E2B, Modal, Daytona, Azure Container Apps Dynamic Sessions — chọn một và đầu tư nghiêm túc. Phần thưởng là agent thông minh hơn, nhanh hơn, rẻ hơn (ít LLM call hơn) — và quan trọng nhất: biết tự sửa lỗi giữa chừng thay vì gọi lại bạn để hỏi.
Nguồn tham khảo
- Wang, X. et al. — Executable Code Actions Elicit Better LLM Agents (ICML 2024)
- Apple Machine Learning Research — CodeAct: Your LLM Agent Acts Better when Generating Code
- Hugging Face — Introducing smolagents: simple agents that write actions in code
- Hugging Face Docs — smolagents documentation
- Hugging Face Agents Course — Writing actions as code snippets or JSON blobs
- GitHub — xingyaoww/code-act — official repo
- GitHub — huggingface/smolagents
Disclaimer: The opinions expressed in this blog are solely my own and do not reflect the views or opinions of my employer or any affiliated organizations. The content provided is for informational and educational purposes only and should not be taken as professional advice. While I strive to provide accurate and up-to-date information, I make no warranties or guarantees about the completeness, reliability, or accuracy of the content. Readers are encouraged to verify the information and seek independent advice as needed. I disclaim any liability for decisions or actions taken based on the content of this blog.