Tool Use & Function Calling — Trang bị công cụ cho AI Agent

1. Từ LLM tĩnh sang Agent có hành động: vì sao cần Tool Use?

Ở các bài trước, chúng ta đã xây dựng được một AI Agent biết trả lời dựa trên kho tri thức RAG. Nhưng doanh nghiệp thực tế cần hơn thế:

“Chatbot đã trả lời đúng rằng đơn hàng đang chờ duyệt — nhưng sao nó không tự tạo ticket hỗ trợ hay gửi email xác nhận luôn được?”

Đây là giới hạn cốt lõi của LLM thuần: mô hình ngôn ngữ chỉ sinh ra văn bản. Nó không thể tự gọi API, truy vấn CSDL, đọc file thời gian thực hay gửi thông báo — trừ khi bạn trang bị cho nó công cụ (Tools).

Tool Use (còn gọi là Function Calling) là cơ chế cho phép LLM:

  1. Nhận diện khi nào cần dùng một công cụ bên ngoài
  2. Tạo ra yêu cầu có cấu trúc (JSON) mô tả công cụ nào cần gọi và với tham số nào
  3. Nhận kết quả từ công cụ đó rồi tổng hợp câu trả lời cuối cùng

Đây là bước chuyển đổi từ LLM “biết nói” sang AI Agent “biết làm” — và là nền tảng của mọi hệ thống tự động hóa thông minh.


2. Mô hình hoạt động của Function Calling

Giao thức Function Calling của OpenAI đã trở thành chuẩn thực tế (de-facto standard) trong ngành. Luồng hoạt động gồm 4 bước rõ ràng:

Người dùng / Orchestrator
         │
         │  [1] User message + danh sách tool definitions (JSON schema)
         ▼
    ┌─────────────┐
    │    LLM      │  Phân tích intent
    │  (GPT-4o,   │  → Quyết định dùng tool nào
    │  Claude...) │  → Tạo tool_call JSON có cấu trúc
    └──────┬──────┘
           │
           │  [2] tool_call: { name: "get_order_status", args: { order_id: "ORD-001" } }
           ▼
    ┌─────────────────────┐
    │   Tool Executor     │  Nhận tool_call từ LLM
    │  (Application Code) │  → Validate args
    │                     │  → Gọi API / DB / service thực tế
    └──────────┬──────────┘
               │
               │  [3] tool_response: { status: "pending", eta: "2026-05-15" }
               ▼
    ┌─────────────┐
    │    LLM      │  Nhận kết quả từ tool
    │  (lần 2)    │  → Tổng hợp câu trả lời tự nhiên
    └──────┬──────┘
           │
           │  [4] Final answer: "Đơn hàng ORD-001 đang chờ duyệt, dự kiến giao 15/05."
           ▼
    Người dùng / Orchestrator

2.1. Chi tiết từng bước

BướcTênAi thực hiệnNội dung
[1]RequestApp codeGửi message + tools[] (JSON schema) lên LLM API
[2]Tool callLLMTrả về finish_reason: "tool_calls" kèm JSON tham số
[3]Tool responseApp codeThực thi tool, gửi kết quả lại API với role: "tool"
[4]Final answerLLMNhận tool result, sinh câu trả lời tự nhiên cho người dùng

Lưu ý quan trọng: LLM không tự gọi tool — nó chỉ tạo ra yêu cầu có cấu trúc. Phần thực thi là do application code của bạn đảm nhiệm. Đây vừa là thiết kế an toàn, vừa là điểm bạn phải tự kiểm soát.


3. Định nghĩa Tool/Function — Cách đặc tả đúng chuẩn

Chất lượng định nghĩa tool quyết định trực tiếp độ chính xác khi LLM chọn và gọi tool. Một tool definition tốt phải có:

  • name: tên ngắn gọn, snake_case, mô tả rõ hành động
  • description: mô tả chi tiết khi nào dùngkhông dùng khi nào — đây là phần quan trọng nhất
  • parameters: JSON Schema đầy đủ, bao gồm type, description, required, enum nếu có

3.1. Ví dụ 3 tool thực tế

Tool 1 — Truy vấn trạng thái đơn hàng (Read)

{
  "type": "function",
  "function": {
    "name": "get_order_status",
    "description": "Tra cứu trạng thái đơn hàng theo mã đơn. Dùng khi khách hỏi về tình trạng, tiến độ hoặc thời gian giao hàng. KHÔNG dùng để sửa hay hủy đơn.",
    "parameters": {
      "type": "object",
      "properties": {
        "order_id": {
          "type": "string",
          "description": "Mã đơn hàng, định dạng ORD-XXXXXX hoặc số nguyên"
        },
        "include_history": {
          "type": "boolean",
          "description": "Có trả về lịch sử trạng thái hay không. Mặc định false.",
          "default": false
        }
      },
      "required": ["order_id"]
    }
  }
}

Tool 2 — Tạo ticket hỗ trợ (Write)

{
  "type": "function",
  "function": {
    "name": "create_support_ticket",
    "description": "Tạo ticket hỗ trợ khi vấn đề không thể giải quyết tự động, hoặc khi khách hàng yêu cầu được hỗ trợ bởi nhân viên. KHÔNG tạo ticket nếu vấn đề đã được giải quyết.",
    "parameters": {
      "type": "object",
      "properties": {
        "customer_id": {
          "type": "string",
          "description": "ID khách hàng trong hệ thống CRM"
        },
        "subject": {
          "type": "string",
          "description": "Tiêu đề ngắn gọn mô tả vấn đề, tối đa 100 ký tự"
        },
        "description": {
          "type": "string",
          "description": "Mô tả chi tiết vấn đề, bao gồm context từ hội thoại"
        },
        "priority": {
          "type": "string",
          "enum": ["low", "medium", "high", "urgent"],
          "description": "Mức độ ưu tiên. Chọn 'urgent' chỉ khi ảnh hưởng đến giao dịch tài chính hoặc sức khỏe."
        },
        "category": {
          "type": "string",
          "enum": ["billing", "shipping", "technical", "product", "other"],
          "description": "Danh mục của ticket"
        }
      },
      "required": ["customer_id", "subject", "description", "priority", "category"]
    }
  }
}

Tool 3 — Gửi thông báo (Notify)

{
  "type": "function",
  "function": {
    "name": "send_notification",
    "description": "Gửi thông báo tới khách hàng qua kênh ưu tiên của họ (email hoặc SMS). Chỉ dùng sau khi đã hoàn thành một hành động cụ thể cần xác nhận. KHÔNG spam — không gửi quá 1 thông báo cho cùng một sự kiện.",
    "parameters": {
      "type": "object",
      "properties": {
        "customer_id": {
          "type": "string",
          "description": "ID khách hàng"
        },
        "channel": {
          "type": "string",
          "enum": ["email", "sms", "zalo", "push"],
          "description": "Kênh gửi thông báo"
        },
        "template_id": {
          "type": "string",
          "description": "ID mẫu thông báo đã được phê duyệt trong hệ thống"
        },
        "variables": {
          "type": "object",
          "description": "Các biến điền vào template, ví dụ: { order_id, status, eta }"
        }
      },
      "required": ["customer_id", "channel", "template_id"]
    }
  }
}

3.2. Nguyên tắc viết description hiệu quả

Nguyên tắcVí dụ xấuVí dụ tốt
Nói rõ khi nào dùng“Lấy thông tin đơn hàng”“Dùng khi khách hỏi về trạng thái, tiến độ hoặc ETA giao hàng”
Nói rõ không dùng khi nào(bỏ qua)“KHÔNG dùng để sửa hay hủy đơn”
Mô tả format tham số“mã đơn”“mã đơn hàng, định dạng ORD-XXXXXX”
Đặt enum cho giá trị giới hạn"type": "string""enum": ["low","medium","high","urgent"]

4. Chiến lược thiết kế bộ công cụ (Tool Catalog)

Khi hệ thống phát triển, bạn sẽ có hàng chục tool. Cần thiết kế có hệ thống ngay từ đầu.

4.1. Phân loại tool theo tác động

NhómĐặc điểmVí dụYêu cầu bảo mật
ReadChỉ đọc, không thay đổi trạng tháiget_order_status, search_product, check_stockThấp — cần authn
WriteThay đổi dữ liệu/trạng thái hệ thốngcreate_ticket, update_order, cancel_subscriptionCao — cần authn + authz + audit log
NotifyGửi thông tin ra bên ngoàisend_email, send_sms, push_notificationTrung bình — cần rate limiting
ComputeTính toán, xử lý dữ liệucalculate_price, generate_report, summarize_dataThấp — cần timeout
ExternalGọi API bên thứ bacall_payment_gateway, query_shipping_apiCao — cần circuit breaker

4.2. Nguyên tắc thiết kế Tool Catalog

Scope — Giới hạn rõ phạm vi:

  • Mỗi tool chỉ làm một việc cụ thể, không làm nhiều thứ trong một lần gọi
  • Tên tool phải nói lên hành động: get_, create_, update_, delete_, send_, calculate_
  • Tránh tool quá generic như do_action hay handle_request

Granularity — Độ chi tiết phù hợp:

  • Tool quá nhỏ → LLM phải gọi nhiều bước → tăng latency, tăng chi phí
  • Tool quá lớn → khó kiểm soát, khó audit, khó reuse
  • Nguyên tắc vàng: một tool nên hoàn thành một business unit of work có thể audit độc lập

Idempotency — Thiết kế để an toàn khi retry:

  • Read tools: luôn idempotent
  • Write tools: bắt buộc phải idempotent (dùng idempotency key)
  • Notify tools: implement deduplication để tránh gửi trùng

4.3. Mẫu Tool Catalog cho dự án Customer Support

Tool Catalog — Customer Support Agent
├── READ
│   ├── get_order_status(order_id)
│   ├── get_customer_profile(customer_id)
│   ├── search_faq(query)
│   └── check_product_availability(sku, quantity)
├── WRITE
│   ├── create_support_ticket(customer_id, subject, description, priority, category)
│   ├── update_ticket_status(ticket_id, status, note)
│   └── schedule_callback(customer_id, datetime, agent_id?)
├── NOTIFY
│   ├── send_notification(customer_id, channel, template_id, variables)
│   └── escalate_to_human(ticket_id, reason, urgency)
└── COMPUTE
    └── calculate_refund_amount(order_id, items, reason)

5. Tool Routing: Cách LLM chọn đúng Tool

Hiểu rõ cơ chế routing giúp bạn thiết kế tool catalog và điều chỉnh khi agent chọn sai tool.

5.1. Ba chế độ tool_choice

Chế độCấu hìnhKhi nào dùng
autotool_choice: "auto"Mặc định — LLM tự quyết định có dùng tool không
requiredtool_choice: "required"Bắt buộc LLM phải gọi ít nhất một tool
forcedtool_choice: { type: "function", function: { name: "get_order_status" } }Buộc LLM gọi đúng tool chỉ định
nonetool_choice: "none"Tắt tool use — LLM chỉ trả lời bằng text

5.2. Parallel Tool Calls

OpenAI GPT-4o và nhiều mô hình hiện đại hỗ trợ gọi nhiều tool song song trong một lần:

// LLM tr v nhiu tool_call cùng lúc
{
  "tool_calls": [
    {
      "id": "call_abc",
      "function": { "name": "get_order_status", "arguments": "{\"order_id\": \"ORD-001\"}" }
    },
    {
      "id": "call_def",
      "function": { "name": "get_customer_profile", "arguments": "{\"customer_id\": \"CUS-123\"}" }
    }
  ]
}

Lợi ích: giảm round-trip, giảm latency đáng kể khi cần nhiều dữ liệu độc lập nhau.
Lưu ý: phải xử lý đồng thời ở phía application code (async/await hoặc Task.WhenAll).

5.3. Kỹ thuật cải thiện độ chính xác routing

  1. Description rõ ràng — đây là tín hiệu chính để LLM phân biệt tool nào phù hợp
  2. Tên tool nhất quán — prefix theo nhóm (get_, create_, send_) giúp LLM nhận pattern
  3. Giới hạn số tool — gửi tối đa 10–15 tool liên quan theo context, không gửi toàn bộ catalog
  4. Tool examples trong system prompt — cung cấp ví dụ khi nào dùng tool nào nếu tool catalog phức tạp
  5. Sử dụng forced tool call trong các workflow có bước cụ thể (ví dụ: bắt buộc verify trước khi write)

6. MCP — Model Context Protocol

6.1. MCP là gì?

MCP (Model Context Protocol) là giao thức mở do Anthropic đề xuất (2024) và được nhiều công ty áp dụng, nhằm chuẩn hóa cách LLM tương tác với tool/resource bên ngoài theo mô hình client–server.

Nếu Function Calling là cơ chế “điện thoại trực tiếp” giữa LLM và một tool, thì MCP là “tổng đài” — một lớp trừu tượng chuẩn hóa, có thể kết nối nhiều tool/resource từ nhiều nguồn.

6.2. Kiến trúc MCP

┌──────────────────────────────────────────────────────────┐
│                     MCP Host (Application)               │
│  ┌─────────────┐                                         │
│  │  LLM / AI   │◀──── tool_call / tool_response          │
│  │   Engine    │                                         │
│  └──────┬──────┘                                         │
│         │ MCP Protocol (JSON-RPC 2.0 over stdio / HTTP)  │
│  ┌──────▼──────────────────────────────────────────┐     │
│  │              MCP Client (built-in)              │     │
│  └──────┬───────────────┬───────────────┬──────────┘     │
└─────────┼───────────────┼───────────────┼────────────────┘
          │               │               │
          ▼               ▼               ▼
   ┌────────────┐  ┌────────────┐  ┌────────────┐
   │ MCP Server │  │ MCP Server │  │ MCP Server │
   │ (Database) │  │ (REST API) │  │ (File Sys) │
   │            │  │            │  │            │
   │ Tools:     │  │ Tools:     │  │ Tools:     │
   │ query_db   │  │ call_crm   │  │ read_file  │
   │ write_db   │  │ send_email │  │ list_files │
   └────────────┘  └────────────┘  └────────────┘

6.3. So sánh Direct Function Call vs MCP

Tiêu chíDirect Function CallingMCP
Độ phức tạp triển khaiThấp — code trực tiếpTrung bình — cần MCP server
Tái sử dụng toolThấp — gắn chặt với appCao — server dùng được với nhiều app/LLM
Tiêu chuẩn hóaMỗi provider khác nhauGiao thức chung, vendor-neutral
Bảo mậtApp tự kiểm soátMCP server có thể implement auth riêng
Phù hợp vớiMVP, tool ít, team nhỏPlatform, nhiều app dùng chung tool
Hỗ trợ streamingCó (SSE)Có (SSE qua HTTP transport)
EcosystemOpenAI, Anthropic, GoogleClaude Desktop, Cursor, VS Code, nhiều IDE

Khuyến nghị thực chiến:

  • Bắt đầu với Direct Function Calling — đơn giản, deploy nhanh, dễ debug
  • Chuyển sang MCP khi có từ 3+ ứng dụng dùng chung tool set, hoặc cần tool marketplace nội bộ

7. So sánh Framework: OpenAI Native vs LangChain vs Semantic Kernel

Tiêu chíOpenAI Native Function CallingLangChain ToolsSemantic Kernel Plugins
Ngôn ngữAny (REST API)Python chính, JS betaC# / Python / Java
Độ phức tạpThấp — gần với API thôTrung bìnhTrung bình–Cao
AbstractionTối thiểuCaoCao
Multi-LLMChỉ OpenAI✅ Nhiều provider✅ Nhiều provider
Agent loopTự code✅ AgentExecutor, LangGraph✅ Planner, Agents
Tool auto-discovery❌ Thủ công✅ Tool registry✅ Plugin auto-import
Memory/State❌ Tự xử lý✅ Memory modules✅ KernelMemory
Enterprise .NETTrung bìnhThấp✅ Xuất sắc
Tài liệu/Community✅ Rất tốt✅ Rất tốt✅ Tốt (Microsoft)
Phù hợp vớiScript nhanh, prototypePython AI teamEnterprise .NET, Azure

8. Triển khai thực tế

8.1. C# — Semantic Kernel Plugin

Semantic Kernel dùng khái niệm Plugin (tương đương Tool Catalog) với attribute-based definition, rất gần với thiết kế .NET:

using Microsoft.SemanticKernel;
using System.ComponentModel;

// ============================================================
// Bước 1: Định nghĩa Plugin với KernelFunction attributes
// ============================================================
public class OrderPlugin
{
    private readonly IOrderService _orderService;
    private readonly ITicketService _ticketService;

    public OrderPlugin(IOrderService orderService, ITicketService ticketService)
    {
        _orderService = orderService;
        _ticketService = ticketService;
    }

    [KernelFunction("get_order_status")]
    [Description("Tra cứu trạng thái đơn hàng theo mã đơn. Dùng khi khách hỏi về tình trạng, tiến độ hoặc ETA giao hàng. KHÔNG dùng để sửa hay hủy đơn.")]
    public async Task<string> GetOrderStatusAsync(
        [Description("Mã đơn hàng, định dạng ORD-XXXXXX")] string orderId,
        [Description("Có trả về lịch sử trạng thái không, mặc định false")] bool includeHistory = false)
    {
        var order = await _orderService.GetOrderAsync(orderId);
        if (order == null)
            return $"Không tìm thấy đơn hàng với mã {orderId}.";

        var result = $"Đơn {orderId}: {order.Status} | ETA: {order.EstimatedDelivery:dd/MM/yyyy}";
        if (includeHistory && order.StatusHistory?.Any() == true)
        {
            var history = string.Join("\n", order.StatusHistory.Select(h => $"  - {h.Date:dd/MM} {h.Status}"));
            result += $"\nLịch sử:\n{history}";
        }
        return result;
    }

    [KernelFunction("create_support_ticket")]
    [Description("Tạo ticket hỗ trợ khi vấn đề cần nhân viên xử lý hoặc không thể tự động giải quyết. KHÔNG tạo ticket nếu đã giải quyết xong.")]
    public async Task<string> CreateSupportTicketAsync(
        [Description("ID khách hàng trong CRM")] string customerId,
        [Description("Tiêu đề ngắn gọn, tối đa 100 ký tự")] string subject,
        [Description("Mô tả chi tiết vấn đề")] string description,
        [Description("Mức ưu tiên: low, medium, high, urgent")] string priority = "medium")
    {
        // Human-in-the-loop: log để audit trước khi write
        Console.WriteLine($"[AUDIT] Creating ticket for customer {customerId}: {subject}");

        var ticket = await _ticketService.CreateAsync(new CreateTicketRequest
        {
            CustomerId = customerId,
            Subject = subject,
            Description = description,
            Priority = Enum.Parse<TicketPriority>(priority, ignoreCase: true)
        });

        return $"Đã tạo ticket #{ticket.Id}. Nhân viên sẽ liên hệ trong {ticket.SlaHours} giờ.";
    }
}

// ============================================================
// Bước 2: Đăng ký Plugin và chạy Agent loop
// ============================================================
public class AgentService
{
    public async Task<string> HandleUserMessageAsync(string userMessage, string customerId)
    {
        // Khởi tạo Kernel
        var builder = Kernel.CreateBuilder();
        builder.AddOpenAIChatCompletion(
            modelId: "gpt-4o-mini",
            apiKey: Environment.GetEnvironmentVariable("OPENAI_API_KEY")!);

        var kernel = builder.Build();

        // Đăng ký plugin
        kernel.Plugins.AddFromObject(
            new OrderPlugin(orderService, ticketService),
            pluginName: "OrderPlugin");

        // Cấu hình auto tool invocation
        var executionSettings = new OpenAIPromptExecutionSettings
        {
            ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions
        };

        // System prompt
        var systemPrompt = $"""
            Bạn là trợ lý hỗ trợ khách hàng của Công ty ABC.
            ID khách hàng hiện tại: {customerId}
            Nguyên tắc:
            - Chỉ sử dụng tool khi thực sự cần thiết
            - Không tạo ticket nếu đã giải quyết được bằng thông tin có sẵn
            - Luôn xác nhận lại với khách trước khi tạo ticket hoặc gửi thông báo
            """;

        var history = new ChatHistory(systemPrompt);
        history.AddUserMessage(userMessage);

        // Gọi LLM với auto tool invocation
        var chatService = kernel.GetRequiredService<IChatCompletionService>();
        var result = await chatService.GetChatMessageContentAsync(
            history,
            executionSettings,
            kernel);

        return result.Content ?? "Xin lỗi, tôi không thể xử lý yêu cầu này lúc này.";
    }
}

8.2. Python — LangChain Tools

LangChain dùng decorator @tool để đăng ký function thành tool, rất Pythonic:

from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from typing import Optional
import logging

logger = logging.getLogger(__name__)

# ============================================================
# Bước 1: Định nghĩa Tools bằng decorator
# ============================================================

@tool
def get_order_status(order_id: str, include_history: bool = False) -> str:
    """Tra cứu trạng thái đơn hàng theo mã đơn.
    Dùng khi khách hỏi về tình trạng, tiến độ hoặc ETA giao hàng.
    KHÔNG dùng để sửa hay hủy đơn.
    
    Args:
        order_id: Mã đơn hàng, định dạng ORD-XXXXXX
        include_history: Có trả về lịch sử trạng thái không, mặc định False
    """
    # Gọi service thực tế (minh hoạ)
    order = order_service.get(order_id)
    if not order:
        return f"Không tìm thấy đơn hàng với mã {order_id}."
    
    result = f"Đơn {order_id}: {order['status']} | ETA: {order['eta']}"
    if include_history and order.get("history"):
        history_str = "\n".join(f"  - {h['date']} {h['status']}" for h in order["history"])
        result += f"\nLịch sử:\n{history_str}"
    return result


@tool
def create_support_ticket(
    customer_id: str,
    subject: str,
    description: str,
    priority: str = "medium",
    category: str = "other"
) -> str:
    """Tạo ticket hỗ trợ khi vấn đề cần nhân viên xử lý.
    KHÔNG tạo ticket nếu đã giải quyết được bằng thông tin có sẵn.
    
    Args:
        customer_id: ID khách hàng trong CRM
        subject: Tiêu đề ngắn gọn (tối đa 100 ký tự)
        description: Mô tả chi tiết vấn đề
        priority: Mức ưu tiên (low/medium/high/urgent)
        category: Danh mục (billing/shipping/technical/product/other)
    """
    # Audit log trước khi write
    logger.info(f"[AUDIT] Creating ticket | customer={customer_id} | priority={priority}")
    
    # Validate priority
    valid_priorities = {"low", "medium", "high", "urgent"}
    if priority not in valid_priorities:
        return f"Priority không hợp lệ. Chọn một trong: {', '.join(valid_priorities)}"
    
    ticket = ticket_service.create({
        "customer_id": customer_id,
        "subject": subject[:100],  # enforce max length
        "description": description,
        "priority": priority,
        "category": category
    })
    
    sla_map = {"urgent": 1, "high": 4, "medium": 8, "low": 24}
    return f"Đã tạo ticket #{ticket['id']}. Nhân viên sẽ phản hồi trong {sla_map[priority]} giờ."


@tool
def search_faq(query: str) -> str:
    """Tìm kiếm trong cơ sở tri thức FAQ.
    Dùng trước khi tạo ticket để xem có thể tự giải quyết không.
    
    Args:
        query: Câu hỏi hoặc từ khoá cần tìm kiếm
    """
    results = rag_service.search(query, top_k=3)
    if not results:
        return "Không tìm thấy thông tin liên quan trong FAQ."
    
    return "\n\n".join(
        f"[{i+1}] {r['title']}\n{r['content'][:300]}..."
        for i, r in enumerate(results)
    )


# ============================================================
# Bước 2: Khởi tạo Agent với tool list
# ============================================================

def create_customer_support_agent(customer_id: str) -> AgentExecutor:
    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
    
    tools = [get_order_status, create_support_ticket, search_faq]
    
    prompt = ChatPromptTemplate.from_messages([
        ("system", f"""Bạn là trợ lý hỗ trợ khách hàng của Công ty ABC.
ID khách hàng: {customer_id}

Nguyên tắc:
1. Luôn tìm kiếm FAQ trước khi tạo ticket
2. Không tạo ticket nếu đã có câu trả lời
3. Xác nhận với khách trước khi thực hiện write action
4. Ưu tiên giải quyết nhanh, chỉ escalate khi thực sự cần"""),
        MessagesPlaceholder(variable_name="chat_history", optional=True),
        ("human", "{input}"),
        MessagesPlaceholder(variable_name="agent_scratchpad"),
    ])
    
    agent = create_openai_tools_agent(llm, tools, prompt)
    
    return AgentExecutor(
        agent=agent,
        tools=tools,
        verbose=True,           # Log tool calls để debug
        max_iterations=5,       # Giới hạn vòng lặp, tránh loop vô hạn
        handle_parsing_errors=True
    )


# ============================================================
# Bước 3: Sử dụng
# ============================================================

async def handle_message(customer_id: str, message: str) -> str:
    agent = create_customer_support_agent(customer_id)
    result = await agent.ainvoke({
        "input": message,
        "chat_history": []
    })
    return result["output"]

9. Bảo mật & Kiểm soát

Tool Use mở ra khả năng hành động thực tế — đây cũng là nơi rủi ro tập trung nhất. Không kiểm soát tốt, agent có thể gây ra hậu quả không mong muốn trong hệ thống nghiệp vụ.

9.1. Allow-list Tool theo context

Không cấp toàn bộ tool catalog cho mọi người dùng và mọi tình huống:

def get_tools_for_context(user_role: str, conversation_stage: str) -> list:
    """Chỉ cấp tool phù hợp với role và giai đoạn hội thoại."""
    
    # Mọi người dùng: chỉ read + search
    base_tools = [get_order_status, search_faq]
    
    # Khách hàng đã xác thực: thêm notify
    if user_role in ("authenticated_customer", "agent"):
        base_tools.append(send_notification)
    
    # Nhân viên CS: thêm write tools
    if user_role == "agent":
        base_tools.extend([create_support_ticket, update_ticket_status])
    
    # Chỉ mở write tools sau khi đã thu thập đủ thông tin
    if conversation_stage == "resolution" and user_role == "agent":
        base_tools.append(schedule_callback)
    
    return base_tools

9.2. Xác thực input trước khi execute

Mỗi tool executor phải validate input — đừng tin tưởng hoàn toàn vào JSON LLM sinh ra:

def execute_tool_safely(tool_name: str, args: dict, user_context: dict) -> dict:
    """Wrapper bảo mật cho mọi tool execution."""
    
    # 1. Kiểm tra tool có trong allow-list không
    allowed = get_tools_for_context(user_context["role"], user_context["stage"])
    if tool_name not in [t.name for t in allowed]:
        return {"error": f"Tool '{tool_name}' không được phép trong context này"}
    
    # 2. Validate schema bằng JSON Schema
    schema = TOOL_SCHEMAS[tool_name]
    errors = jsonschema.validate(args, schema["parameters"])
    if errors:
        return {"error": f"Invalid arguments: {errors}"}
    
    # 3. Business rule validation
    if tool_name == "create_support_ticket":
        if args.get("priority") == "urgent" and user_context["role"] != "agent":
            args["priority"] = "high"  # Downgrade nếu không có quyền
    
    # 4. Audit log trước khi execute write
    if TOOL_WRITE_FLAG.get(tool_name):
        audit_log.write({
            "timestamp": datetime.utcnow().isoformat(),
            "user_id": user_context["user_id"],
            "tool": tool_name,
            "args": sanitize_pii(args),  # Mask PII trước khi log
            "session_id": user_context["session_id"]
        })
    
    # 5. Execute
    return TOOL_REGISTRY[tool_name](**args)

9.3. Human-in-the-Loop trước Write Tools

Với các hành động có tác động cao (hủy đơn, hoàn tiền, cập nhật hợp đồng), bắt buộc phải có bước xác nhận từ người:

# Pseudo workflow: human_approval_gate
name: write_tool_approval_flow
trigger: tool_call_detected

steps:
  - name: classify_tool_impact
    check:
      - tool_name in HIGH_IMPACT_TOOLS  # cancel_order, process_refund, update_contract
    on_match: require_approval
    on_no_match: auto_execute

  - name: request_human_approval
    action: send_approval_request
    channels:
      - slack_manager_channel
      - email_supervisor
    timeout: 30_minutes
    payload:
      tool: "{{ tool_name }}"
      args: "{{ tool_args_sanitized }}"
      context: "{{ conversation_summary }}"
      requested_by: "{{ agent_id }}"

  - name: wait_for_decision
    action: pause_workflow
    resume_on:
      - approved: execute_tool_with_audit_log
      - rejected: notify_agent_and_user
      - timeout: escalate_to_level2

on_error:
  - default_action: reject_and_notify
  - audit_log: always

9.4. Checklist bảo mật Tool Use

  • Allow-list tool theo role và context người dùng
  • Validate JSON schema của mọi tool call trước khi execute
  • Mask PII (email, phone, CCCD) trong audit log
  • Rate limiting per user/session trên write tools
  • Human approval gate cho high-impact write operations
  • Circuit breaker cho external API calls
  • Timeout toàn bộ tool execution (gợi ý: ≤ 10 giây)
  • Dead letter queue cho failed tool calls
  • Alert khi có pattern bất thường (nhiều urgent ticket trong 5 phút)

10. Checklist thiết kế Tool Use

✅ Checklist định nghĩa Tool

  • Tên tool rõ ràng, dùng prefix hành động (get_, create_, send_)
  • Description mô tả đủ “khi nào dùng” VÀ “khi nào không dùng”
  • Mọi parameter có description đầy đủ, có enum cho giá trị giới hạn
  • Mọi required parameter được đánh dấu rõ
  • Tool đã được test với ít nhất 10 câu hỏi thực tế để kiểm tra routing
  • Tool không làm nhiều hơn một việc (single responsibility)

✅ Checklist triển khai

  • Allow-list tool đã thiết lập theo role và context
  • Validation schema cho tất cả input
  • Audit log đã bật cho mọi write tool
  • Timeout đã thiết lập (khuyến nghị 10s cho tool, 30s cho toàn bộ agent loop)
  • Parallel tool call đã được xử lý đúng bằng async
  • Max iteration đã đặt giới hạn (5–10 bước là đủ cho hầu hết use case)
  • Error handling: tool failure không làm crash toàn bộ conversation
  • Idempotency key đã implement cho write tools

✅ Checklist vận hành

  • Dashboard theo dõi: tool call rate, error rate, latency per tool
  • Alert khi tool error rate > 5%
  • Định kỳ review audit log để phát hiện misuse
  • Kiểm tra định kỳ tool definitions vẫn còn phù hợp với API backend
  • Quy trình rollback khi tool gây ra kết quả không mong muốn

11. KPI, Chi phí và ROI

11.1. KPI cho Tool Use

KPIĐịnh nghĩaMục tiêu MVPMục tiêu Production
Tool Call Accuracy% tool call LLM chọn đúng tool cần thiết≥ 85%≥ 95%
Tool Execution Success Rate% tool call thực thi thành công (không lỗi)≥ 90%≥ 99%
Avg Tool Latency (P95)Thời gian thực thi tool P95≤ 3s≤ 1s
Automation Rate% yêu cầu giải quyết hoàn toàn tự động≥ 50%≥ 75%
Human Escalation Rate% request cần human intervention≤ 30%≤ 15%
Unnecessary Tool Calls% lần LLM gọi tool không cần thiết≤ 15%≤ 5%

11.2. Ước lượng chi phí (Quy mô SMB, 5.000 requests/ngày)

Hạng mụcChi phí thiết lậpChi phí vận hành/thángGhi chú
LLM API (GPT-4o-mini)$60–150Tool use tăng ~30% token vs text-only
Backend API / Tool servers$0 (code hiện có)$20–50Thêm endpoint cho tool execution
Logging & monitoring$10–30Elasticsearch hoặc Datadog
Audit log storage$5–15S3/MinIO lưu audit log
Tổng ước lượng$500–2.000$95–245Bao gồm thiết lập & tích hợp ban đầu

11.3. ROI tham chiếu

Tình huống: Bộ phận CS xử lý 150 yêu cầu/ngày, mỗi request mất trung bình 8 phút nhân sự.

  • Trước Tool Use: 150 × 8 phút = 20 giờ/ngày → ~$600/ngày (tính $30/giờ)
  • Sau Tool Use (70% automation): 45 request cần người → 6 giờ/ngày → ~$180/ngày
  • Tiết kiệm: ~$420/ngày × 22 ngày = ~$9.240/tháng
  • Chi phí hệ thống: ~$200/tháng
  • ROI tháng đầu: ~4.500% | Hoàn vốn thiết lập: < 1 tuần

12. Rủi ro và Phương án Giảm Thiểu

Rủi roMức độXác suấtPhương án giảm thiểu
LLM gọi sai tool (nhầm create khi chỉ cần get)CaoTrung bìnhDescription rõ ràng + unit test routing + allow-list
Hallucinate tham số (bịa order_id không tồn tại)CaoThấp–Trung bìnhValidate schema + verify tham số với DB trước khi execute
Prompt injection qua user input để gọi tool ngoài scopeRất caoThấpSanitize input + allow-list tool + human approval cho write
Tool chạy vòng lặp vô hạnCaoThấpMax iteration limit + circuit breaker
Chi phí token tăng đột biếnTrung bìnhTrung bìnhToken budget per session + cảnh báo anomaly
API backend bị quá tải do nhiều parallel tool callsTrung bìnhTrung bìnhRate limit + queue + circuit breaker
Lộ PII trong tool args logRất caoTrung bìnhPII masking trước khi log + RBAC trên log access
Tool definition out-of-date khi backend API thay đổiTrung bìnhCaoVersioning tool definitions + integration test tự động

13. Roadmap Triển Khai 3 Giai Đoạn

Giai đoạn 1 (Tuần 1–3): Tool Use cơ bản

Mục tiêu: Agent có thể đọc dữ liệu nghiệp vụ và trả lời chính xác hơn.

  • Xác định 3–5 read tools quan trọng nhất (tra cứu đơn hàng, khách hàng, FAQ)
  • Viết tool definitions theo chuẩn JSON Schema với description đầy đủ
  • Tích hợp OpenAI Function Calling hoặc Semantic Kernel cơ bản
  • Test routing với 50 câu hỏi mẫu đại diện
  • Thiết lập logging tool calls cơ bản
  • KPI đo được: Tool Call Accuracy ≥ 85%, Tool Latency ≤ 3s

Giai đoạn 2 (Tuần 4–8): Write Tools + Bảo mật

Mục tiêu: Agent có thể thực hiện hành động nghiệp vụ có kiểm soát.

  • Thêm 3–5 write tools (tạo ticket, gửi thông báo, cập nhật trạng thái)
  • Triển khai allow-list tool theo role và context
  • Implement audit log đầy đủ cho write operations
  • Thiết lập human approval gate cho high-impact actions
  • Kiểm thử bảo mật: prompt injection, schema validation
  • KPI đo được: Automation Rate ≥ 50%, Escalation Rate ≤ 30%

Giai đoạn 3 (Tuần 9–12): Tối ưu & Scale

Mục tiêu: Vận hành ổn định, chi phí tối ưu, có thể mở rộng tool catalog.

  • Parallel tool calls cho các read operations độc lập
  • Tối ưu tool selection bằng dynamic context filtering
  • Xem xét MCP nếu cần chia sẻ tool cho nhiều ứng dụng
  • Dashboard KPI đầy đủ và alert tự động
  • A/B test tool descriptions để cải thiện routing accuracy
  • KPI đo được: Tool Call Accuracy ≥ 95%, Automation Rate ≥ 75%

14. Kết luận và Kết nối sang Bài 5

Tool Use & Function Calling là bước nhảy vọt từ chatbot trả lời sang AI Agent hành động. Khi triển khai đúng:

  • Agent không chỉ biết — mà còn làm được
  • Hệ thống không chỉ phản hồi — mà còn tự động hóa được quy trình nghiệp vụ
  • ROI không chỉ là “tiện hơn” — mà đo được bằng số giờ nhân lực tiết kiệm mỗi ngày

Ba nguyên tắc cốt lõi để Tool Use thành công trong thực tế:

  1. Description trước, code sau — 80% vấn đề routing đến từ description mơ hồ, không phải lỗi kỹ thuật
  2. Read trước, Write sau — triển khai read tools, đo ROI, rồi mới mở rộng sang write
  3. Audit everything — mọi hành động của agent lên hệ thống nghiệp vụ đều phải có dấu vết

Bài tiếp theo trong series sẽ đi sâu vào Memory & Context Management — cách AI Agent ghi nhớ thông tin qua nhiều phiên hội thoại, quản lý long-context hiệu quả và xây dựng hồ sơ người dùng thông minh để cá nhân hóa trải nghiệm. Đây là yếu tố then chốt để đi từ “agent trả lời được một câu” sang “agent hiểu khách hàng theo thời gian”.


Tác giả: AI Agent Series | Cập nhật: 14/05/2026

Previous
Next