CodeAct 2026: Khi AI Agent Viết Code Thay Vì Gọi JSON Tool

Posted on: 5/14/2026 10:31:20 AM

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?

+20%Success rate so với JSON tool call (CodeAct paper)
-30%Số step (đồng nghĩa giảm 30% LLM call)
7+Sandbox runtime hỗ trợ Smolagents (E2B, Modal, Pyodide...)
2024Năm CodeAct được công bố — đến 2026 thành mainstream

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

2022 — ReAct (Yao et al., Princeton + Google)
Bài paper kinh điển định nghĩa pattern Thought → Action → Observation. Action ban đầu chỉ là text (search query, click). Đặt nền cho mọi agent framework sau này nhưng chưa ràng buộc format.
06/2023 — OpenAI Function Calling
OpenAI release function calling API: LLM được fine-tune để xuất JSON hợp lệ với schema cho trước. Ngành lập tức chuẩn hoá theo pattern này — Anthropic, Google, Mistral đều copy. Một LLM call = một tool call.
02/2024 — CodeAct paper (Wang et al., UIUC)
"Executable Code Actions Elicit Better LLM Agents" — chứng minh trên 17 LLM rằng output code thay vì JSON tăng success rate tới 20% và giảm 30% số bước trên benchmark API-Bank.
12/2024 — Hugging Face Smolagents
HF release library agent code-first by default. CodeAgent là class chính, ToolCallingAgent chỉ là alternative. Đây là tín hiệu mainstream đầu tiên.
2025 — Manus AI, OpenHands
Manus AI gây sốt với general agent dùng code execution làm action space duy nhất. OpenHands (kế thừa OpenDevin) cũng chuyển sang CodeAct.
2026 — Code Execution chuẩn ngành
Anthropic's bash + code execution tool, OpenAI Code Interpreter as a built-in, Apple Machine Learning Research công nhận CodeAct là pattern hiệu quả nhất. JSON tool call vẫn còn nhưng dần co cụm về trường hợp đơn giản hoặc model nhỏ chưa biết viết code.

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

  1. 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.
  2. 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 .
  3. 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.
  4. 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.
  5. 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 CallCodeAct
Định dạng outputJSON object hợp schemaĐoạn Python (Markdown code block)
Số tool/step1Nhiề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ừngPhải quay về LLM mỗi lần failtry/except ngay trong code, không tốn LLM call
ComposabilityThấp — mỗi tool độc lậpCao — output tool A là input tool B trực tiếp
Yêu cầu LLMBất kỳ model có function callingModel 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
DebugTrace JSON dễ đọcCần log code + stdout + variable state
Latency mỗi stepThấp (1 tool call)Cao hơn ( cold start ~50-200ms)
Tổng latency end-to-endCao (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:

RuntimeCơ chế cô lậpCold startStatefulPhù hợp
E2BFirecracker microVM~150msProduction agent, multi-tenant
ModalgVisor + container~500msWorkload nặng tính toán/GPU
DaytonaContainer + LXC~200msDev environment + agent
Azure Container Apps Dynamic SessionsHyper-V + Code Interpreter~300msCó (60 phút)Enterprise Microsoft stack
Pyodide + DenoWebAssembly + permission flag~50msKhó (per-call)Edge, lightweight, single-tenant
DockerLinux namespaces (yếu hơn microVM)~1-2sDev/POC, không production multi-tenant
Local Python (KHÔNG dùng)Không có0msKhông bao giờ

5 checklist bảo mật bắt buộc

  1. Network egress filter: chặn outbound trừ allowlist (tránh data exfiltration).
  2. Filesystem read-only trừ /tmp; không mount secret vào .
  3. CPU + memory + wall-clock limit: hạn 30s, 512MB — đủ task hợp lệ, chặn loop vô hạn.
  4. AST validation trước khi exec: chặn __import__, exec, eval, open("/etc/passwd").
  5. 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