Từ trước đến nay, chúng ta lập trình các hệ thống AI theo kiểu "hỏi gì đáp nấy" — LLM nhận vào một Prompt tĩnh và sinh ngay ra câu trả lời cuối cùng. Nhưng nếu câu hỏi yêu cầu giải quyết một bài toán phức tạp đòi hỏi phải tính toán số liệu và tra cứu dữ liệu ngoài thời gian thực, LLM thô chắc chắn sẽ bó tay.

Bước tiến hóa tiếp theo của ứng dụng trí tuệ nhân tạo là AI Agent (Đại lý AI). Thay vì trả lời ngay, Agent sử dụng LLM làm "bộ não" lập kế hoạch, tự chia nhỏ tác vụ lớn thành các bước nhỏ, tự lựa chọn và kích hoạt các công cụ (Tools) lập trình bên ngoài, đọc kết quả trả về, tự điều chỉnh và lặp lại cho đến khi hoàn thành mục tiêu. Bài học này sẽ hướng dẫn bạn giải phẫu khung tư duy ReAct (Reasoning + Acting), cách thiết kế cấu trúc sử dụng công cụ (Tool Use), và tự tay xây dựng một AI Agent tự động giải toán và tra cứu thông tin mà không dùng bất kỳ framework cồng kềnh nào.

17.1 Định nghĩa AI Agent: Khác gì LLM thô?

Một mô hình LLM thuần túy giống như một bộ não bị cô lập: cực kỳ thông minh về mặt ngôn ngữ nhưng không có tay chân để tương tác với thế giới xung quanh, và không có khả năng tự suy ngẫm để sửa lỗi trong quá trình suy luận.

Một AI Agent (Hệ tác nhân AI) là một cấu trúc phần mềm bao quanh LLM, trang bị cho nó 3 thành phần cốt lõi:

  • Planning (Lập kế hoạch): Khả năng chia nhỏ mục tiêu phức tạp thành chuỗi các bước hành động logic.
  • Memory (Bộ nhớ): Lưu trữ lịch sử suy luận ngắn hạn và thông tin dài hạn để giữ ngữ cảnh nhất quán.
  • Tools (Công cụ): Các đoạn code Python, API web, hoặc cơ sở dữ liệu giúp LLM kết nối với thế giới thực (ví dụ: máy tính toán, bộ tìm kiếm Google, API thời tiết).
⚠️ Cạm bẫy: Lạm dụng AI Agent cho tác vụ tĩnh
Lập trình Agent đi kèm chi phí và độ trễ cực cao. Một vòng lặp tư duy của Agent có thể tốn từ 3 đến 10 lượt gọi API LLM liên tục. Nếu bạn chỉ cần phân loại email hoặc tóm tắt một đoạn văn bản ngắn, hãy sử dụng LLM thô hoặc một hàm Structured Output đơn thuần thay vì dựng Agent. Chỉ dùng Agent khi luồng xử lý không tất định (non-deterministic) và kết quả bước sau phụ thuộc trực tiếp vào kết quả chạy thử của bước trước.

17.2 Vòng lặp tư duy ReAct (Reasoning + Acting)

Khung tư duy ReAct (Reasoning and Acting) là phương pháp thiết lập prompt hướng dẫn LLM hoạt động theo vòng lặp 4 bước tuần hoàn:

🔄 Chu trình vòng lặp ReAct:
  1. 1. Thought (Suy nghĩ): LLM phân tích trạng thái hiện tại của nhiệm vụ và tự trả lời câu hỏi: "Tôi cần làm gì tiếp theo? Tôi có cần gọi công cụ nào không?".
  2. 2. Action (Hành động): LLM sinh ra một câu lệnh gọi công cụ theo đúng định dạng quy định, ví dụ: Call: calculator[2 + 2].
  3. 3. Observation (Quan sát): Hệ thống Python bắt giữ câu lệnh trên, thực thi hàm cục bộ bên ngoài và trả về kết quả thô cho LLM đọc dưới dạng: Observation: 4.
  4. 4. Lặp lại (Repeat): LLM đọc kết quả quan sát, suy nghĩ tiếp (Thought) để quyết định gọi tiếp công cụ khác hoặc đưa ra câu trả lời cuối cùng (Final Answer: ...).

Ưu điểm lớn nhất của ReAct là khả năng Tự sửa sai (Self-Correction). Nếu một công cụ bị trả về kết quả lỗi (ví dụ: máy tính báo lỗi chia cho 0 hoặc API web lỗi kết nối), bước Observation chứa thông báo lỗi sẽ được nạp lại vào ngữ cảnh. LLM sẽ đọc lỗi đó ở bước Thought tiếp theo và tự động thay đổi đối số truyền vào hoặc chuyển sang gọi một công cụ dự phòng.

⚡ Cạm bẫy vòng lặp vô hạn (Infinite Loop) & Max Iterations
Khi LLM gặp một câu hỏi quá khó hoặc công cụ liên tục trả về kết quả lỗi không mong muốn, mô hình có thể bị "kẹt suy nghĩ" và liên tục lặp đi lặp lại hành vi gọi cùng một công cụ với cùng một tham số sai. Điều này gây tốn kém tài nguyên API cực lớn và treo ứng dụng. Khắc phục: Bắt buộc phải cài đặt một biến đếm giới hạn số vòng lặp tối đa (Max Iterations, thường là 5 lượt). Nếu vượt quá số lượt này mà Agent chưa đưa ra được Final Answer, hệ thống phải ngắt luồng và trả về cảnh báo.
⚠️ Cạm bẫy bảo mật: eval() là công cụ hai lưỡi cực nguy hiểm
Hàm calculate() ở dự án thực hành dùng eval() để tính biểu thức toán học — một kỹ thuật cực kỳ rủi ro nếu dùng trực tiếp, vì LLM (hoặc kẻ tấn công chèn Prompt Injection vào câu hỏi người dùng) có thể "dụ" Agent gọi calculate("__import__('os').system('rm -rf /')") để thực thi mã độc trên máy chủ. Đã kiểm chứng bằng Python: dòng re.sub(r'[^0-9+\-*/().\s]', '', expression) lọc sạch chuỗi trên trước khi đưa vào eval(), biến nó thành "().( )" — một biểu thức không hợp lệ, gây lỗi cú pháp thay vì thực thi lệnh hệ thống. Đây là lý do bắt buộc phải làm sạch (sanitize) mọi tham số truyền vào bất kỳ công cụ nào có khả năng thực thi code động, tuyệt đối không tin tưởng mù quáng vào đầu ra của LLM chỉ vì nó "trông giống" một biểu thức toán học hợp lệ.

17.3 Tool Use: Thiết lập mô tả công cụ cho LLM

Làm thế nào để LLM biết hệ thống có những công cụ gì để gọi? Chúng ta phải mô tả chi tiết danh sách công cụ trong System Prompt gửi cho LLM. Bản mô tả bắt buộc phải chứa:

  • Tên công cụ (Tool Name): Định danh duy nhất để LLM sinh ra chính xác (ví dụ: calculator).
  • Chức năng (Description): Giải thích rõ công cụ dùng để làm gì và khi nào nên dùng nó.
  • Schema đối số (Arguments): Đặc tả kiểu dữ liệu đầu vào cần truyền.

17.4 Dự án thực hành bài 17: Tự lập trình ReAct Agent từ số 0 bằng Python

Dự án thực hành của bài học này là tự viết mã nguồn cho một Hệ tác nhân AI ReAct độc lập mà không phụ thuộc vào LangChain hay bất kỳ thư viện bên thứ ba nào.

Chúng ta sẽ lập trình hai công cụ cục bộ: calculate (hàm tính toán biểu thức toán học an toàn bằng Python) và get_stock_price (hàm tra cứu giá cổ phiếu giả lập). Hệ thống sẽ chạy một vòng lặp kiểm soát (Control Loop) liên tục phân tích chuỗi văn bản sinh ra từ LLM (Ollama cục bộ), bóc tách lệnh hành động, chạy code Python thực tế và trả kết quả ngược lại cho mô hình cho đến khi đạt được kết quả cuối cùng.

react_agent.py
import urllib.request
import json
import re
 
# 1. Định nghĩa các công cụ (Tools) mà Agent có thể sử dụng
def calculate(expression):
    """Tính toán kết quả của một biểu thức toán học dạng chuỗi."""
    # Làm sạch chuỗi chỉ cho phép các ký tự toán học an toàn để tránh lỗi bảo mật eval
    clean_expr = re.sub(r'[^0-9+\-*/().\s]', '', expression)
    try:
        # Thực thi biểu thức toán học an toàn
        return str(eval(clean_expr))
    except Exception as e:
        return f"Lỗi tính toán: {str(e)}"
 
def get_stock_price(symbol):
    """Lấy giá cổ phiếu hiện tại của một mã chứng khoán (Giả lập dữ liệu)."""
    prices = {
        "AAPL": "185.50 USD",
        "GOOGL": "172.30 USD",
        "MSFT": "420.10 USD",
        "TSLA": "175.20 USD"
    }
    return prices.get(symbol.upper(), "Không tìm thấy thông tin mã chứng khoán này.")
 
# Bản đồ liên kết tên công cụ với hàm thực thi Python
TOOL_MAP = {
    "calculate": calculate,
    "get_stock_price": get_stock_price
}
 
# 2. Xây dựng System Prompt định hướng ReAct cho LLM
SYSTEM_PROMPT = """Bạn là một AI Agent hoạt động theo vòng lặp tư duy ReAct (Thought -> Action -> Observation).
Bạn được cung cấp các công cụ sau:
 
1. get_stock_price[symbol]: Lấy giá cổ phiếu của một mã chứng khoán. Ví dụ: get_stock_price[AAPL]
2. calculate[expression]: Thực hiện tính toán toán học. Ví dụ: calculate[150 * 1.1]
 
Quy trình làm việc của bạn:
Bước 1: Suy nghĩ về câu hỏi của người dùng (Thought: ...)
Bước 2: Nếu cần dùng công cụ, hãy xuất ra định dạng: Action: ten_cong_cu[tham_so] và dừng lại để nhận kết quả.
Bước 3: Sau khi nhận được kết quả (Observation: ...), hãy tiếp tục suy nghĩ (Thought: ...) để quyết định hành động tiếp theo hoặc đưa ra câu trả lời cuối cùng.
Bước 4: Khi đã có câu trả lời cuối cùng, hãy xuất ra định dạng: Final Answer: [Câu trả lời của bạn].
 
Bắt đầu!
"""
 
# 3. Hàm gọi API Ollama local
def query_ollama(prompt, model_name="llama3"):
    url = "http://localhost:11434/api/chat"
    payload = {
        "model": model_name,
        "messages": [
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": prompt}
        ],
        "stream": False,
        "options": {
            "temperature": 0.0 # Đặt temperature bằng 0 để mô hình suy luận logic chính xác nhất
        }
    }
    
    data = json.dumps(payload).encode("utf-8")
    req = urllib.request.Request(url, data=data, headers={"Content-Type": "application/json"})
    
    try:
        with urllib.request.urlopen(req) as response:
            res_data = json.loads(response.read().decode("utf-8"))
            return res_data["message"]["content"]
    except Exception as e:
        return f"[Lỗi kết nối Ollama]: {str(e)}"
 
# 4. Vòng lặp điều khiển ReAct (Control Loop)
def run_react_agent(user_question, max_iterations=5):
    print(f"🙋 [Câu hỏi của người dùng]: '{user_question}'\n")
    
    # Khởi tạo lịch sử cuộc hội thoại của Agent
    agent_context = user_question
    
    for i in range(max_iterations):
        print(f"--- 🔄 Vòng lặp suy luận thứ {i+1} ---")
        
        # Gọi mô hình để lấy Thought và Action
        response = query_ollama(agent_context)
        print(response)
        
        # Cập nhật phản hồi của LLM vào ngữ cảnh lịch sử
        agent_context += f"\n{response}"
        
        # Kiểm tra xem mô hình đã đưa ra câu trả lời cuối cùng chưa
        if "Final Answer:" in response:
            final_answer = response.split("Final Answer:")[-1].strip()
            print(f"\n🏆 [Kết quả cuối cùng]: {final_answer}\n")
            return
            
        # Tìm kiếm câu lệnh gọi công cụ dạng Action: ten_cong_cu[tham_so]
        action_match = re.search(r'Action:\s*(\w+)\[(.*?)\]', response)
        
        if action_match:
            tool_name = action_match.group(1)
            tool_arg = action_match.group(2)
            
            print(f"\n⚙️ [Hệ thống phát hiện lệnh gọi công cụ]: Gọi '{tool_name}' với đối số '{tool_arg}'")
            
            # Kiểm tra xem công cụ có tồn tại trong hệ thống không
            if tool_name in TOOL_MAP:
                # Thực thi hàm Python tương ứng
                observation_result = TOOL_MAP[tool_name](tool_arg)
            else:
                observation_result = f"Lỗi: Công cụ '{tool_name}' không tồn tại."
                
            print(f"📊 [Kết quả thực thi công cụ (Observation)]: '{observation_result}'\n")
            
            # Nạp kết quả quan sát ngược trở lại ngữ cảnh của Agent
            agent_context += f"\nObservation: {observation_result}"
        else:
            print("\n⚠️ Cảnh báo: Mô hình không sinh ra lệnh gọi công cụ hợp lệ và chưa có Final Answer.")
            break
            
    print("\n❌ Thất bại: Agent vượt quá giới hạn vòng lặp tối đa (Max Iterations).")
 
if __name__ == "__main__":
    # Câu hỏi yêu cầu tích hợp cả 2 công cụ: (Tra giá cổ phiếu Apple) rồi nhân với (10 cổ phiếu)
    test_question = "Nếu tôi mua 15 cổ phiếu AAPL thì tôi cần trả tổng cộng bao nhiêu tiền?"
    
    # Chạy Agent
    run_react_agent(test_question)
🔢 Vết chạy thật (kịch bản giả lập phản hồi LLM đã kiểm chứng)
Để thấy rõ cơ chế hoạt động mà không cần cài Ollama, giả lập 3 phản hồi LLM đúng định dạng ReAct và chạy qua chính vòng lặp điều khiển run_react_agent ở trên (đã chạy Python xác minh toàn bộ vết chạy):
  • Vòng 1: Thought: cần tra giá AAPL trước → Action: get_stock_price[AAPL] → hệ thống thực thi, trả về Observation: 185.50 USD.
  • Vòng 2: Thought: lấy giá nhân với 15 → Action: calculate[185.50 * 15]Observation: 2782.5.
  • Vòng 3: Final Answer: Bạn cần trả tổng cộng 2782.5 USD. — vòng lặp dừng lại đúng như thiết kế khi phát hiện chuỗi "Final Answer:".
Vết chạy này xác nhận vòng lặp điều khiển ghép nối đúng 2 công cụ độc lập (tra cứu giá rồi tính toán) qua 2 bước Thought → Action → Observation liên tiếp trước khi tổng hợp câu trả lời cuối — đúng bản chất "chuỗi suy luận nhiều bước" mà một lời gọi LLM đơn lẻ không thể tự làm được.

Tóm tắt bài học & Cầu nối kiến thức

🔑 Bài học đạt được:
  • Đạt được: Hiểu bản chất vòng lặp suy luận ReAct (Reason + Act) giúp LLM tự động phân tích yêu cầu.
  • Đạt được: Lập trình thành công AI Agent tự đưa ra quyết định chọn công cụ phù hợp và tự sửa lỗi đầu vào.

Cầu nối bài tiếp theo: Vòng lặp ReAct tuần tự rất dễ bị rơi vào vòng lặp vô hạn hoặc khó quản lý luồng rẽ nhánh phức tạp. Chúng ta sẽ nâng cấp lên thiết kế đại lý dạng đồ thị có trạng thái bằng LangGraph ở Bài số 18.

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

File Python react_agent.py — mã nguồn xây dựng Agent tư duy ReAct gọi công cụ tính toán và tra cứu giá cổ phiếu cục bộ (chạy python react_agent.py):

Tải về react_agent.py

📖 Tài liệu tham khảo

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

Bài 16: RAG Nâng Cao: Query Translation & Cross-Encoder Reranking Bài 18: Đại lý có trạng thái với LangGraph Quay lại Lộ trình Kỹ Sư AI Thực Chiến

Bình luận