Mở đầu: Track C bắt đầu — tự tay viết framework agent từ số 0

6 bài trước (Track A) nói về cách một AI product ra đời ở tầm tổ chức. Từ bài này, series chuyển hẳn góc nhìn: không còn là "quy trình công nghiệp" nữa, mà là kỹ sư hệ thống tự viết — xây dựng từng viên gạch của một framework agent thu nhỏ, giống việc tự implement một LangChain/LangGraph mini bằng JS thuần. Bài 7 này là viên gạch đầu tiên: tool-calling — cơ chế cho phép agent làm những việc mà bản thân model ngôn ngữ không tự làm được.

Toàn bộ code trong Track C (Bài 7–9) nằm trong 1 file dùng chung aisys-agent-kernel.js — mỗi bài mở rộng thêm 1 phần, và các bài sau tái sử dụng nguyên phần đã viết ở bài trước (không viết lại). Bài 7 xây phần ToolRegistry.


📚 Điều kiện tiên quyết
JavaScript ES6+ cơ bản (class, module). Không cần đã đọc Track A, nhưng nếu muốn hiểu bối cảnh tổng thể, xem Bài 6 — bài cuối của Track A.

1. Vì Sao Agent Cần Tool

Một model ngôn ngữ thuần chỉ có thể sinh văn bản dựa trên những gì nó đã học được lúc huấn luyện — nó không thể tính toán chính xác số học lớn, không biết thời tiết hôm nay, không đọc được file trên máy bạn, và kiến thức của nó luôn có một "điểm dừng" (knowledge cutoff). Tool-calling giải quyết đúng giới hạn này: cho phép model yêu cầu gọi một hàm/API bên ngoài để lấy dữ liệu thật hoặc thực hiện hành động thật, rồi dùng kết quả đó để trả lời chính xác hơn.

💡 Mẹo: Tool-calling không phải "model tự chạy code"
Model KHÔNG tự thực thi tool — nó chỉ sinh ra một "ý định" (tên tool + tham số). Chương trình của bạn (agent runtime) mới là bên thực sự gọi hàm, kiểm tra kết quả, rồi đưa quan sát đó lại cho model. Tách bạch này chính là điểm cho phép bạn kiểm soát an toàn (mục 3) — model không có quyền truy cập trực tiếp vào hệ thống thật.

2. Tool Schema (JSON Schema)

Để model "biết" cách gọi một tool, tool đó cần một schema mô tả rõ tên, mục đích, và kiểu dữ liệu từng tham số — thường ở định dạng gần giống JSON Schema:

tool-definition.js
const calculatorTool = defineTool({
  name: "calculator",
  description: "Tính biểu thức số học cơ bản (+ - * / và dấu ngoặc)",
  schema: {
    type: "object",
    required: ["expression"],
    properties: {
      // "pattern" đóng vai trò SANDBOX — chỉ cho phép ký tự số học, xem mục 3.
      expression: { type: "string", pattern: "^[0-9+\\-*/(). ]+$" },
    },
  },
  execute: (args) => safeCalculate(args.expression),
});

Schema này phục vụ 2 việc: (1) cho model biết CHÍNH XÁC cách gọi tool (tên tham số, kiểu dữ liệu), và (2) cho runtime một quy tắc để validate — kiểm tra tham số trước khi thực thi, thay vì tin tưởng mù quáng bất kỳ thứ gì model sinh ra.

3. Validate Input & Sandbox Hoá Side-Effect

Đây là mục quan trọng nhất về mặt an toàn: model có thể bị dẫn dắt (qua prompt injection, hoặc đơn giản là "ảo giác") để sinh ra tham số độc hại. Runtime tuyệt đối KHÔNG được tin tưởng input từ model mà không kiểm tra:

validate-args.js
function validateArgs(schema, args) {
  for (const key of schema.required || []) {
    if (!(key in args)) throw new ToolValidationError(`Thiếu tham số bắt buộc: "${key}"`);
  }
  for (const [key, value] of Object.entries(args)) {
    const propSchema = schema.properties[key];
    if (!propSchema) throw new ToolValidationError(`Tham số không được khai báo: "${key}"`);
    if (propSchema.pattern && !new RegExp(propSchema.pattern).test(value)) {
      // Chặn NGAY tại đây — dù model có sinh ra "1); require('fs')..." cũng
      // không thể lọt qua vì không khớp pattern chỉ cho phép ký tự số học.
      throw new ToolValidationError(`Tham số "${key}" vi phạm sandbox pattern`);
    }
  }
}
🕳️ Cạm bẫy: Tin tưởng schema thôi chưa đủ, phải sandbox cả bên THỰC THI
Chỉ validate tham số ĐẦU VÀO là chưa đủ nếu bản thân hàm execute vẫn dùng cơ chế nguy hiểm bên trong. Ví dụ: dù đã validate expression chỉ chứa ký tự số học, nếu tool calculator triển khai bằng eval(expression) hay new Function(expression) — một pattern validate lỏng lẻo (hoặc một lỗi ở chính regex) vẫn có thể dẫn tới thực thi mã tuỳ ý. Đây là lý do demo ở mục 5 dùng một bộ tính toán TỰ VIẾT (recursive-descent parser), không dùng eval/Function ở bất kỳ đâu — sandbox thật sự nằm ở việc loại bỏ hoàn toàn khả năng thực thi mã tuỳ ý, không chỉ dựa vào 1 lớp validate đầu vào.

4. Tool Registry Pattern

Khi có nhiều tool, agent cần một nơi tập trung để đăng ký, tra cứu, và gọi tool theo tên — gọi là tool registry. Registry giúp thêm tool mới mà không cần sửa logic agent:

tool-registry.js
class ToolRegistry {
  constructor() { this.tools = new Map(); }
  register(tool) { this.tools.set(tool.name, tool); }
  get(name) { return this.tools.get(name); }
  list() { return [...this.tools.values()]; }
  execute(name, args) {
    const tool = this.get(name);
    if (!tool) throw new ToolValidationError(`Không tìm thấy tool: "${name}"`);
    validateArgs(tool.schema, args); // luôn validate TRƯỚC khi thực thi
    return tool.execute(args);
  }
}

Đây chính xác là class dùng trong demo bên dưới, và sẽ được Agent (Bài 9) gọi lại nguyên vẹn — không viết lại logic registry ở bài sau.

5. Thực hành tương tác: Tool Registry Playground

Chọn 1 preset bên dưới (hoặc tự chọn tool + nhập tham số) để xem registry validate rồi thực thi. Đặc biệt thử preset "🧪 Thử tấn công" — một chuỗi cố tình chứa mã độc hại, và xem sandbox pattern chặn nó lại trước khi chạm tới bất kỳ hàm thực thi nào:

🧰 Tool Registry Playground
Schema của tool đang chọn

                  
Tool đã đăng ký trong registry
    aisys-tool-lab.js (trích)
    import { defineTool, ToolRegistry, safeCalculate } from "./aisys-agent-kernel.js";
    
    export function createDemoRegistry() {
      const registry = new ToolRegistry();
      registry.register(defineTool({ name: "calculator", /* ... */ execute: (a) => safeCalculate(a.expression) }));
      registry.register(defineTool({ name: "dictionary", /* ... */ execute: (a) => DICTIONARY[a.term] }));
      return registry;
    }

    Bài 8 sẽ mở rộng chính file aisys-agent-kernel.js này với Memory và Prompt Template — hai viên gạch tiếp theo agent cần trước khi có thể tự chạy vòng lặp suy luận đầy đủ ở Bài 9.

    Tải file code thực hành minh họa bài học

    File JavaScript aisys-agent-kernel.js — phần ToolRegistry/safeCalculate dùng chung cho cả Track C, cùng aisys-tool-lab.js wiring demo của bài này:

    Tải về aisys-agent-kernel.js

    📖 Tài liệu tham khảo

    Bài viết liên quan trong series

    Bài 6: Model Versioning, Rollback & Chi Phí Hạ Tầng Bài 8: Memory & Prompt Template Engine Quay lại Lộ trình Kỹ Thuật Hệ Thống AI

    Bình luận