DEV Community

Cover image for Hướng Dẫn Tự Tạo Mã Claude
Sebastian Petrus
Sebastian Petrus

Posted on • Originally published at apidog.com

Hướng Dẫn Tự Tạo Mã Claude

Điểm chính

Vụ rò rỉ mã nguồn Claude Code đã tiết lộ một codebase TypeScript gồm 512.000 dòng vào ngày 31 tháng 3 năm 2026. Kiến trúc cốt lõi là một vòng lặp while gọi API Claude, phân phối các lệnh gọi công cụ và trả về kết quả. Bạn có thể tự xây dựng phiên bản của riêng mình với Python, Anthropic SDK và khoảng 200 dòng mã cho vòng lặp chính. Hướng dẫn này sẽ phân tích từng thành phần và chỉ cho bạn cách tái tạo chúng.

Hãy dùng thử Apidog ngay hôm nay

Giới thiệu

Vào ngày 31 tháng 3 năm 2026, Anthropic đã phát hành một tệp source map 59,8 MB bên trong phiên bản 2.1.88 của gói npm @anthropic-ai/claude-code. Source map là các tạo phẩm gỡ lỗi giúp đảo ngược mã JavaScript đã được rút gọn về mã nguồn gốc. Vì công cụ xây dựng của Anthropic (Bun's bundler) tạo ra các tệp này theo mặc định, toàn bộ codebase TypeScript đã có thể được phục hồi.

Trong vòng vài giờ, các nhà phát triển đã sao chép mã nguồn trên hàng tá kho lưu trữ GitHub. Cộng đồng nhanh chóng mổ xẻ mọi module, từ vòng lặp agent chính đến các tính năng ẩn như "chế độ bí mật" và tiêm công cụ giả.

Phản ứng chia làm nhiều luồng. Một số người chỉ trích các thực hành bảo mật của Anthropic. Những người khác bị cuốn hút bởi kiến trúc. Nhưng phản ứng hiệu quả nhất đến từ các nhà phát triển đã hỏi: "Tôi có thể tự xây dựng cái này không?"

Câu trả lời là có. Các mẫu cốt lõi rất đơn giản. Hướng dẫn này sẽ đi sâu vào từng lớp kiến trúc, giải thích lý do tại sao Anthropic đưa ra các lựa chọn đó, và cung cấp mã hoạt động bạn có thể sử dụng làm điểm khởi đầu. Bạn cũng sẽ học cách kiểm tra các tương tác API của agent tùy chỉnh của mình với Apidog, điều này giúp gỡ lỗi các cuộc hội thoại API nhiều lượt dễ dàng hơn nhiều so với các lệnh curl thô.

Những gì vụ rò rỉ tiết lộ về kiến trúc của Claude Code

Tổng quan về codebase

Claude Code (tên nội bộ “Tengu”) gồm ~1.900 tệp, chia module như sau:

cli/          - Giao diện người dùng Terminal (React + Ink)
tools/        - Hơn 40 triển khai công cụ
core/         - Lời nhắc hệ thống, quyền, hằng số
assistant/    - Điều phối Agent
services/     - Các lệnh gọi API, nén, OAuth, đo từ xa
Enter fullscreen mode Exit fullscreen mode

CLI là ứng dụng React chạy qua Ink. Yoga được dùng cho layout, mã ANSI cho styling. Mọi giao diện đều là component React.

Tuy nhiên, phần này không cần thiết cho agent DIY. Bạn chỉ cần một vòng lặp REPL đơn giản.

Vòng lặp agent chính

Bỏ qua UI và đo từ xa, cốt lõi của Claude Code là một vòng lặp while. Quy trình:

  1. Gửi tin nhắn đến API Claude (lời nhắc hệ thống + định nghĩa công cụ)
  2. Nhận phản hồi (văn bản và/hoặc các khối tool_use)
  3. Thực thi từng công cụ qua một bản đồ phân phối tên → hàm xử lý
  4. Thêm kết quả công cụ vào danh sách tin nhắn
  5. Nếu còn lệnh gọi công cụ, lặp lại từ bước 1
  6. Nếu chỉ còn văn bản trả về, xuất ra cho người dùng

Một chu trình như vậy gọi là "lượt". Lặp cho đến khi Claude trả ra văn bản thuần tuý.

Phiên bản tối thiểu bằng Python:

import anthropic

client = anthropic.Anthropic()
MODEL = "claude-sonnet-4-6"

def agent_loop(system_prompt: str, tools: list, messages: list) -> str:
    """Vòng lặp agent chính - tiếp tục gọi cho đến khi không còn sử dụng công cụ nào nữa."""
    while True:
        response = client.messages.create(
            model=MODEL,
            max_tokens=16384,
            system=system_prompt,
            tools=tools,
            messages=messages,
        )

        messages.append({"role": "assistant", "content": response.content})

        if response.stop_reason != "tool_use":
            return "".join(
                block.text for block in response.content
                if hasattr(block, "text")
            )

        tool_results = []
        for block in response.content:
            if block.type == "tool_use":
                result = execute_tool(block.name, block.input)
                tool_results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": result,
                })

        messages.append({"role": "user", "content": tool_results})
Enter fullscreen mode Exit fullscreen mode

Phần lớn sự phức tạp còn lại là ở các công cụ, quyền, quản lý ngữ cảnh và bộ nhớ.

Xây dựng hệ thống công cụ

Tại sao nên dùng công cụ chuyên dụng thay vì bash

Claude Code sử dụng các công cụ chuyên biệt cho thao tác tệp (vd: Read, Edit, Grep, Glob) thay vì chỉ dùng bash. Lý do:

  • Đầu ra có cấu trúc: Dễ phân tích và nhất quán, tránh đầu ra không đoán trước của bash.
  • An toàn: BashTool bị giới hạn, còn công cụ chuyên dụng tránh tiêm mã.
  • Tiết kiệm token: Đầu ra công cụ có thể cắt ngắn, còn cat dễ lãng phí token.

Bộ công cụ thiết yếu

Tối thiểu cần năm công cụ sau:

TOOLS = [
    {
        "name": "read_file",
        "description": "Đọc một tệp từ hệ thống tệp. Trả về nội dung kèm số dòng.",
        "input_schema": {...}
    },
    {
        "name": "write_file",
        "description": "Ghi nội dung vào một tệp. Tạo tệp nếu nó không tồn tại.",
        "input_schema": {...}
    },
    {
        "name": "edit_file",
        "description": "Thay thế một chuỗi cụ thể trong một tệp. old_string phải là duy nhất.",
        "input_schema": {...}
    },
    {
        "name": "run_command",
        "description": "Thực thi một lệnh shell và trả về stdout/stderr.",
        "input_schema": {...}
    },
    {
        "name": "search_code",
        "description": "Tìm kiếm một mẫu regex trong các tệp trong một thư mục.",
        "input_schema": {...}
    }
]
Enter fullscreen mode Exit fullscreen mode

(Tham khảo định nghĩa schema đầy đủ ở phần trên.)

Phân phối hàm xử lý công cụ

Ánh xạ tên công cụ tới hàm xử lý:

import subprocess
import os
import re

def execute_tool(name: str, params: dict) -> str:
    handlers = {
        "read_file": handle_read_file,
        "write_file": handle_write_file,
        "edit_file": handle_edit_file,
        "run_command": handle_run_command,
        "search_code": handle_search_code,
    }

    handler = handlers.get(name)
    if not handler:
        return f"Lỗi: Công cụ không xác định '{name}'"

    try:
        return handler(params)
    except Exception as e:
        return f"Lỗi: {str(e)}"

# ... (Xem chi tiết các hàm handle_* ở phần trên)
Enter fullscreen mode Exit fullscreen mode

Quản lý ngữ cảnh

Tại sao ngữ cảnh quan trọng hơn prompt

Claude Code đầu tư nhiều vào quản lý ngữ cảnh hơn là prompt hệ thống. Có hai chiến lược cần thiết:

  • Tự động nén khi gần đạt giới hạn token, dành một vùng đệm cho phần tóm tắt.
  • Tái chèn CLAUDE.md cho nguyên tắc dự án ở mỗi lượt.

Trình nén đơn giản

def maybe_compact(messages: list, system_prompt: str, max_tokens: int = 180000) -> list:
    total_chars = sum(
        len(str(m.get("content", ""))) for m in messages
    )
    estimated_tokens = total_chars // 4

    if estimated_tokens < max_tokens * 0.85:
        return messages

    summary_response = client.messages.create(
        model=MODEL,
        max_tokens=4096,
        system="Summarize this conversation...",
        messages=messages,
    )

    summary_text = summary_response.content[0].text

    compacted = [
        {"role": "user", "content": f"[Tóm tắt cuộc hội thoại]\n{summary_text}"},
        {"role": "assistant", "content": "Tôi có ngữ cảnh từ cuộc hội thoại trước. Tôi nên làm gì tiếp theo?"},
    ]
    compacted.extend(messages[-4:])
    return compacted
Enter fullscreen mode Exit fullscreen mode

Tái chèn ngữ cảnh dự án

def build_system_prompt(project_dir: str) -> str:
    base_prompt = """Bạn là một trợ lý lập trình giúp đỡ trong các nhiệm vụ kỹ thuật phần mềm.
Bạn có quyền truy cập vào các công cụ..."""

    claude_md_path = os.path.join(project_dir, ".claude", "CLAUDE.md")
    if os.path.exists(claude_md_path):
        with open(claude_md_path, "r") as f:
            project_context = f.read()
        base_prompt += f"\n\n# Nguyên tắc dự án\n{project_context}"

    root_md = os.path.join(project_dir, "CLAUDE.md")
    if os.path.exists(root_md):
        with open(root_md, "r") as f:
            root_context = f.read()
        base_prompt += f"\n\n# Nguyên tắc kho lưu trữ\n{root_context}"

    return base_prompt
Enter fullscreen mode Exit fullscreen mode

Hệ thống bộ nhớ ba lớp

Lớp 1: MEMORY.md (luôn được tải)

Một chỉ mục nhỏ luôn nằm trong prompt hệ thống:

- [Tùy chọn người dùng](memory/user-prefs.md) - ưu tiên TypeScript, sử dụng phím tắt Vim
- [Quy ước API](memory/api-conventions.md) - REST với JSON:API, snake_case
- [Quy trình triển khai](memory/deploy.md) - dùng GitHub Actions, AWS EKS
Enter fullscreen mode Exit fullscreen mode

Lớp 2: Tệp chủ đề (tải theo yêu cầu)

Các tệp chi tiết chỉ được tải nếu liên quan.

Lớp 3: Bản ghi phiên (chỉ tìm kiếm)

Không bao giờ tải toàn bộ log, chỉ tìm kiếm theo id.

Hệ thống bộ nhớ tối thiểu

import json

MEMORY_DIR = ".agent/memory"

def load_memory_index() -> str:
    index_path = os.path.join(MEMORY_DIR, "MEMORY.md")
    if os.path.exists(index_path):
        with open(index_path, "r") as f:
            return f.read()
    return ""

def save_memory(key: str, content: str, description: str):
    os.makedirs(MEMORY_DIR, exist_ok=True)
    filename = f"{key.replace(' ', '-').lower()}.md"
    filepath = os.path.join(MEMORY_DIR, filename)
    with open(filepath, "w") as f:
        f.write(f"---\nname: {key}\ndescription: {description}\n---\n\n{content}")

    index_path = os.path.join(MEMORY_DIR, "MEMORY.md")
    index_lines = []
    if os.path.exists(index_path):
        with open(index_path, "r") as f:
            index_lines = f.readlines()
    new_entry = f"- [{key}]({filename}) - {description}\n"
    updated = False
    for i, line in enumerate(index_lines):
        if filename in line:
            index_lines[i] = new_entry
            updated = True
            break
    if not updated:
        index_lines.append(new_entry)

    with open(index_path, "w") as f:
        f.writelines(index_lines)
Enter fullscreen mode Exit fullscreen mode

Đừng quên thêm save_memory vào danh sách công cụ.

Thêm hệ thống cấp phép

Ba mức rủi ro — thấp, trung bình, cao — cho từng công cụ:

RISK_LEVELS = {
    "read_file": "low",
    "search_code": "low",
    "edit_file": "medium",
    "write_file": "medium",
    "run_command": "high",
}

def check_permission(tool_name: str, params: dict, auto_approve_low: bool = True) -> bool:
    risk = RISK_LEVELS.get(tool_name, "high")

    if risk == "low" and auto_approve_low:
        return True

    print(f"\n--- Kiểm tra quyền (rủi ro {risk.upper()}) ---")
    print(f"Công cụ: {tool_name}")
    for key, value in params.items():
        display = str(value)[:200]
        print(f"  {key}: {display}")

    response = input("Cho phép? [y/n/luôn luôn]: ").strip().lower()
    if response == "always":
        RISK_LEVELS[tool_name] = "low"
        return True
    return response == "y"
Enter fullscreen mode Exit fullscreen mode

Kiểm tra các lệnh gọi API của agent của bạn với Apidog

Xây dựng agent lập trình đồng nghĩa với việc phải gửi hàng trăm request API tới Claude. Gỡ lỗi các cuộc hội thoại nhiều lượt rất khó với log thô.

Ảnh chụp màn hình Apidog minh họa việc kiểm tra lệnh gọi API cho một cuộc hội thoại nhiều lượt.

Apidog giúp kiểm thử và kiểm tra các request API chính xác agent của bạn gửi đi. Cách sử dụng:

Chụp và phát lại yêu cầu API

  1. Mở Apidog, tạo project mới cho agent
  2. Nhập endpoint API Anthropic: POST https://api.anthropic.com/v1/messages
  3. Thiết lập body với prompt hệ thống, công cụ, messages
  4. Phát lại từng lượt, thay đổi tham số nếu cần

Bạn có thể cô lập vòng sử dụng công cụ mà không cần chạy toàn bộ agent. Khi có lỗi tool hoặc tham số, chỉnh sửa body ngay trong Apidog rồi gửi lại.

Gỡ lỗi hội thoại nhiều lượt

Apidog hỗ trợ lưu trạng thái messages thành biến môi trường:

  • Lưu snapshot mảng messages sau mỗi lượt
  • Phát lại từ bất kỳ thời điểm nào
  • So sánh kết quả giữa các lần chạy

Xác thực schema công cụ

Định nghĩa công cụ (JSON Schema) sai dễ gây lỗi ẩn. Nhập schema vào Apidog, dùng validator của nó để phát hiện lỗi trước khi gửi API. Tải Apidog để tăng tốc gỡ lỗi agent.

Tổng hợp tất cả: REPL hoàn chỉnh

Agent tối thiểu dạng REPL:

#!/usr/bin/env python3
"""Một agent lập trình tối thiểu theo phong cách Claude Code."""

import anthropic
import os
import sys

client = anthropic.Anthropic()
MODEL = "claude-sonnet-4-6"
PROJECT_DIR = os.getcwd()

def main():
    system_prompt = build_system_prompt(PROJECT_DIR)
    memory = load_memory_index()
    if memory:
        system_prompt += f"\n\n# Bộ nhớ\n{memory}"

    messages = []
    print("Agent lập trình đã sẵn sàng. Gõ 'quit' để thoát.\n")

    while True:
        user_input = input("> ").strip()
        if user_input.lower() in ("quit", "exit"):
            break
        if not user_input:
            continue

        messages.append({"role": "user", "content": user_input})

        messages = maybe_compact(messages, system_prompt)

        current_system = build_system_prompt(PROJECT_DIR)
        memory = load_memory_index()
        if memory:
            current_system += f"\n\n# Bộ nhớ\n{memory}"

        result = agent_loop(current_system, TOOLS, messages)
        print(f"\n{result}\n")

if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

Chỉ dưới 300 dòng Python, bạn đã có agent đọc/chỉnh sửa tệp, chạy lệnh, tìm kiếm codebase, quản lý ngữ cảnh và bộ nhớ giữa các phiên.

Những gì cần thêm tiếp theo

  • Agent phụ/ song song: Chạy các tác vụ độc lập trong agent phụ, tránh ô nhiễm context.
  • Khử trùng lặp đọc tệp: Theo dõi tệp đã đọc, không đọc lại nếu chưa thay đổi.
  • Cắt ngắn/lấy mẫu đầu ra: Cắt output lớn, thông báo số lượng kết quả bị bỏ qua.
  • Tự động nén với tái chèn tệp: Sau tóm tắt hội thoại, tái chèn nội dung tệp truy cập gần đây.

Những gì chúng ta học được từ vụ rò rỉ

  • Vòng lặp agent rất đơn giản: 30 dòng code là đủ cho core loop.
  • Công cụ chuyên dụng tốt hơn bash: Đầu ra có cấu trúc, an toàn, tiết kiệm token.
  • Bộ nhớ cần nhiều lớp: Chỉ mục luôn tải, tệp chủ đề on-demand, log chỉ tìm kiếm.
  • Quản lý ngữ cảnh là trọng tâm: Nén tự động, tái chèn nguyên tắc dự án và cắt ngắn đầu ra là chìa khoá.
  • Phần khung (harness) mới là sản phẩm chính: Mô hình chỉ là "bộ não", còn agent framework mới là giá trị sản phẩm.

Nếu bạn muốn kiểm tra và gỡ lỗi các tương tác API của agent tùy chỉnh của mình, bao gồm các cuộc hội thoại sử dụng công cụ nhiều lượt, các schema yêu cầu phức tạp và xác thực phản hồi, hãy dùng thử Apidog miễn phí. Nó xử lý việc gỡ lỗi API để bạn có thể tập trung vào logic của agent.

Câu hỏi thường gặp

Tôi có thể hợp pháp sử dụng các mẫu từ vụ rò rỉ mã nguồn Claude Code không?

Vụ rò rỉ tiết lộ các mẫu kiến trúc, không phải thuật toán độc quyền. Xây dựng agent sử dụng vòng lặp while và phân phối công cụ là mẫu chuẩn của Anthropic API. Không nên copy mã nguồn nhưng hoàn toàn có thể tái tạo kiến trúc bằng mã của mình.

Tôi nên sử dụng mô hình nào cho một agent lập trình tự làm (DIY)?

Claude Sonnet 4.6 cân bằng tốc độ và khả năng. Claude Opus 4.6 mạnh hơn với quyết định phức tạp nhưng tốn kém hơn. Tác vụ đơn giản có thể dùng Claude Haiku 4.5 (tiết kiệm ~90%).

Chi phí để chạy agent lập trình của riêng bạn là bao nhiêu?

Một phiên (30-50 lượt) với Claude Sonnet 4.6 tốn khoảng 1-5$. Chi phí chính là kích thước context window; nén mạnh giúp giảm giá. Claude Code kích hoạt nén khi dùng 92% context.

Tại sao Claude Code lại sử dụng React cho một ứng dụng terminal?

Ink cho phép tái sử dụng mô hình component/state của React cho UI terminal phức tạp. DIY chỉ cần REPL input()/print() là đủ.

Tính năng quan trọng nhất cần xây dựng sau vòng lặp cốt lõi là gì?

Hệ thống cấp phép. Nếu không, mô hình có thể ghi đè tệp/thực thi lệnh tuỳ ý. Một bước xác nhận trước khi ghi/thực thi là tối thiểu.

Claude Code xử lý lỗi từ các lệnh gọi công cụ như thế nào?

Lỗi tool trả về dưới dạng nội dung tin nhắn tool_result. Mô hình quyết định thử lại, chuyển hướng hoặc hỏi người dùng. Không có xử lý lỗi đặc biệt ở code.

Tôi có thể sử dụng cái này với các mô hình khác ngoài Claude không?

Có. Mẫu sử dụng công cụ hoạt động với mọi mô hình hỗ trợ gọi hàm: GPT-4, Gemini, Llama v.v. Chỉ cần điều chỉnh định dạng API.

Làm cách nào để ngăn agent chạy các lệnh nguy hiểm?

Dùng danh sách chặn (rm -rf /, mkfs, ...) và yêu cầu phê duyệt cho mọi lệnh run_command. Phân loại rủi ro cho mọi hoạt động, xây dựng logic tương tự Claude Code.

Top comments (0)