Phiên bản thay thế: Tham khảo phiên bản sử dụng Interactions API của hướng dẫn này.
Việc quan sát một AI Agent tự động chỉnh sửa nhiều tệp tin, chạy các câu lệnh, xử lý lỗi và lặp đi lặp lại để giải quyết một vấn đề có vẻ rất phức tạp, thậm chí giống như phép thuật. Nhưng thực tế không phải vậy. Bí mật để xây dựng một Agent chính là... chẳng có bí mật nào cả.
Cốt lõi của một Agent thực sự đơn giản đến kinh ngạc: Đó là một Mô hình Ngôn ngữ Lớn (LLM) chạy trong một vòng lặp, được trang bị các công cụ mà nó có thể tự chọn để sử dụng.
Nếu bạn có thể viết một vòng lặp trong Python, bạn hoàn toàn có thể xây dựng một Agent. Hướng dẫn này sẽ dẫn dắt bạn đi qua từng bước, từ một lệnh gọi API đơn giản cho đến một CLI Agent (Agent chạy trên giao diện dòng lệnh) hoàn chỉnh.
Thực chất Agent là gì?
Trong khi các quy trình phần mềm truyền thống mang tính chỉ định và tuân theo các lộ trình đã định sẵn (Bước A -> Bước B -> Bước C), thì Agent là một hệ thống sử dụng LLM để quyết định luồng điều khiển một cách linh hoạt nhằm đạt được mục tiêu của người dùng.
Một Agent thường bao gồm các thành phần cốt lõi sau:
- Mô hình (Bộ não): Công cụ suy luận, trong trường hợp này là mô hình Gemini. Nó suy luận qua các thông tin mơ hồ, lập kế hoạch cho các bước và quyết định khi nào cần sự trợ giúp từ bên ngoài.
- Công cụ (Bàn tay và Đôi mắt): Các hàm mà Agent có thể thực thi để tương tác với thế giới/môi trường bên ngoài (ví dụ: tìm kiếm web, đọc tệp tin, gọi API).
- Ngữ cảnh/Bộ nhớ (Không gian làm việc): Thông tin mà Agent có quyền truy cập tại bất kỳ thời điểm nào. Việc quản lý hiệu quả phần này được gọi là Context Engineering (Kỹ nghệ Ngữ cảnh).
- Vòng lặp (Sự sống): Một vòng lặp while cho phép mô hình thực hiện chu trình: Quan sát → Suy nghĩ → Hành động → Quan sát lại, cho đến khi nhiệm vụ hoàn tất.
"Vòng lặp" (The Loop) của hầu hết mọi agent là một quá trình lặp đi lặp lại:
- Định nghĩa các Công cụ (Define Tool Definitions): Bạn mô tả các công cụ có sẵn (ví dụ: get_weather) cho mô hình bằng định dạng JSON có cấu trúc.
- Gọi LLM: Bạn gửi yêu cầu của người dùng cùng với các định nghĩa công cụ đó cho mô hình.
- Mô hình Quyết định: Mô hình phân tích yêu cầu. Nếu cần dùng công cụ, nó sẽ trả về một cấu trúc tool use (sử dụng công cụ) bao gồm tên công cụ và các tham số đi kèm.
- Thực thi Công cụ (Trách nhiệm của Phía máy khách): Mã nguồn ứng dụng (client) sẽ chặn lệnh "tool use" này, thực thi đoạn mã hoặc gọi API thực tế và thu thập kết quả.
- Phản hồi và Lặp lại: Bạn gửi kết quả (tool response) ngược lại cho mô hình. Mô hình sẽ sử dụng thông tin mới này để quyết định bước tiếp theo: hoặc là gọi một công cụ khác, hoặc đưa ra câu trả lời cuối cùng.
Xây dựng một Agent
Hãy cùng xây dựng một agent theo từng bước, tiến triển từ việc tạo văn bản cơ bản đến một CLI agent hoàn chỉnh bằng cách sử dụng Gemini 3 Pro và Python SDK.
Điều kiện tiên quyết: Cài đặt SDK (pip install google-genai) và thiết lập biến môi trường GEMINI_API_KEY (Bạn có thể lấy mã này tại AI Studio).
Bước 1: Tạo văn bản Cơ bản và Trừu tượng hóa
Bước đầu tiên là tạo ra một tương tác nền tảng với LLM (ở đây là Gemini 3 Pro). Chúng ta sẽ tạo một lớp trừu tượng Agent đơn giản để cấu trúc mã nguồn, lớp này sẽ được mở rộng xuyên suốt hướng dẫn. Đầu tiên, chúng ta sẽ bắt đầu với một chatbot đơn giản có khả năng duy trì lịch sử trò chuyện.
from google import genai
from google.genai import types
class Agent:
def __init__(self, model: str):
self.model = model
self.client = genai.Client()
self.contents = []
def run(self, contents: str):
self.contents.append({"role": "user", "parts": [{"text": contents}]})
response = self.client.models.generate_content(model=self.model, contents=self.contents)
self.contents.append(response.candidates[0].content)
return response
agent = Agent(model="gemini-3-pro-preview")
response1 = agent.run(
contents="Xin chào, 3 thành phố hàng đầu nên ghé thăm ở Đức là gì? Chỉ trả về tên các thành phố."
)
print(f"Model: {response1.text}")
# Kết quả: Berlin, Munich, Cologne
response2 = agent.run(
contents="Hãy kể cho tôi nghe về thành phố thứ hai."
)
print(f"Model: {response2.text}")
# Kết quả: Munich là thủ phủ của bang Bavaria và nổi tiếng với lễ hội Oktoberfest.
Đây vẫn chưa phải là một agent. Nó chỉ là một chatbot tiêu chuẩn. Nó có khả năng duy trì trạng thái hội thoại nhưng không thể thực hiện hành động, nói cách khác là chưa có "bàn tay hay đôi mắt".
Bước 2: Trang bị Bàn tay & Đôi mắt (Sử dụng Công cụ - Tool Use)
Để bắt đầu biến chatbot này thành một agent, chúng ta cần tính năng Tool Use (Sử dụng công cụ) hoặc Function Calling (Gọi hàm). Chúng ta cung cấp cho agent các công cụ. Việc này đòi hỏi phải xác định phần thực thi (mã Python) và phần định nghĩa (schema mà LLM nhìn thấy). Nếu LLM tin rằng công cụ đó sẽ giúp giải quyết yêu cầu của người dùng, nó sẽ trả về một yêu cầu có cấu trúc để gọi hàm đó thay vì chỉ trả về văn bản thuần túy.
Chúng ta sẽ tạo ra 3 công cụ:
read_file, write_file, và list_dir. Một Định nghĩa Công cụ (Tool Definition) là một JSON schema xác định tên (name), mô tả (description), và các tham số (parameters) của công cụ.Thực hành tốt nhất (Best Practice): Sử dụng các trường description để giải thích khi nào và làm thế nào để sử dụng công cụ. Mô hình phụ thuộc rất nhiều vào các mô tả này để hiểu ngữ cảnh sử dụng. Hãy viết thật rõ ràng và chi tiết.
import os import json # Định nghĩa công cụ đọc tệp read_file_definition = { "name": "read_file", "description": "Đọc một tệp tin và trả về nội dung của nó.", "parameters": { "type": "object", "properties": { "file_path": { "type": "string", "description": "Đường dẫn đến tệp cần đọc.", } }, "required": ["file_path"], }, } # Định nghĩa công cụ liệt kê thư mục list_dir_definition = { "name": "list_dir", "description": "Liệt kê nội dung của một thư mục.", "parameters": { "type": "object", "properties": { "directory_path": { "type": "string", "description": "Đường dẫn đến thư mục cần liệt kê.", } }, "required": ["directory_path"], }, } # Định nghĩa công cụ ghi tệp write_file_definition = { "name": "write_file", "description": "Ghi một tệp tin với nội dung cho trước.", "parameters": { "type": "object", "properties": { "file_path": { "type": "string", "description": "Đường dẫn đến tệp cần ghi.", }, "contents": { "type": "string", "description": "Nội dung cần ghi vào tệp.", }, }, "required": ["file_path", "contents"], }, } # Các hàm thực thi tương ứng def read_file(file_path: str) -> str: with open(file_path, "r") as f: return f.read() def write_file(file_path: str, contents: str) -> bool: with open(file_path, "w") as f: f.write(contents) return True def list_dir(directory_path: str) -> list[str]: full_path = os.path.expanduser(directory_path) return os.listdir(full_path) # Tập hợp các công cụ file_tools = { "read_file": {"definition": read_file_definition, "function": read_file}, "write_file": {"definition": write_file_definition, "function": write_file}, "list_dir": {"definition": list_dir_definition, "function": list_dir}, }
Bây giờ chúng ta tích hợp các công cụ và lệnh gọi hàm vào lớp Agent.
from google import genai
from google.genai import types
class Agent:
def __init__(self, model: str, tools: list[dict]):
self.model = model
self.client = genai.Client()
self.contents = []
self.tools = tools
def run(self, contents: str):
self.contents.append({"role": "user", "parts": [{"text": contents}]})
# Cấu hình các công cụ để gửi cho mô hình
config = types.GenerateContentConfig(
tools=[types.Tool(function_declarations=[tool["definition"] for tool in self.tools.values()])],
)
response = self.client.models.generate_content(model=self.model, contents=self.contents, config=config)
self.contents.append(response.candidates[0].content)
return response
agent = Agent(model="gemini-3-pro-preview", tools=file_tools)
response = agent.run(
contents="Bạn có thể liệt kê các tệp tin của tôi trong thư mục hiện tại không?"
)
print(response.function_calls)
# Kết quả: [FunctionCall(name='list_dir', arguments={'directory_path': '.'})]
Tuyệt vời! Mô hình đã gọi công cụ thành công. Bây giờ, chúng ta cần thêm logic thực thi công cụ vào lớp Agent và tạo vòng lặp để trả kết quả về cho mô hình.
Bước 3: Hoàn tất Vòng lặp (The Agent)
Một Agent không chỉ dừng lại ở việc tạo ra một lệnh gọi công cụ duy nhất, mà là tạo ra một chuỗi các lệnh gọi, gửi kết quả ngược lại cho mô hình, rồi tiếp tục tạo ra lệnh gọi khác... cho đến khi nhiệm vụ hoàn thành.
Lớp Agent sẽ xử lý vòng lặp cốt lõi này: chặn lệnh FunctionCall, thực thi công cụ ở phía máy khách (client-side) và gửi lại FunctionResponse. Chúng ta cũng thêm một SystemInstruction (Chỉ dẫn hệ thống) để định hướng cho mô hình biết phải làm gì.
Lưu ý: Gemini 3 sử dụng Thought signatures (Chữ ký tư duy) để duy trì ngữ cảnh suy luận qua các lần gọi API. Bạn phải gửi các chữ ký này ngược lại cho mô hình trong yêu cầu của mình, chính xác như khi nhận được chúng.
# ... Mã nguồn cho các công cụ và định nghĩa công cụ từ Bước 2 nằm ở đây ...
from google import genai
from google.genai import types
class Agent:
def __init__(self, model: str, tools: list[dict], system_instruction: str = "You are a helpful assistant."):
self.model = model
self.client = genai.Client()
self.contents = []
self.tools = tools
self.system_instruction = system_instruction
def run(self, contents: str | list[dict[str, str]]):
# Xử lý nội dung đầu vào (văn bản thuần túy hoặc kết quả từ hàm)
if isinstance(contents, list):
self.contents.append({"role": "user", "parts": contents})
else:
self.contents.append({"role": "user", "parts": [{"text": contents}]})
config = types.GenerateContentConfig(
system_instruction=self.system_instruction,
tools=[types.Tool(function_declarations=[tool["definition"] for tool in self.tools.values()])],
)
response = self.client.models.generate_content(model=self.model, contents=self.contents, config=config)
self.contents.append(response.candidates[0].content)
# Nếu mô hình yêu cầu gọi hàm (công cụ)
if response.function_calls:
functions_response_parts = []
for tool_call in response.function_calls:
print(f"[Function Call] {tool_call}")
if tool_call.name in self.tools:
# Thực thi hàm thực tế với các tham số từ mô hình
result = {"result": self.tools[tool_call.name]["function"](**tool_call.args)}
else:
result = {"error": "Tool not found"}
print(f"[Function Response] {result}")
functions_response_parts.append({"functionResponse": {"name": tool_call.name, "response": result}})
# Đệ quy: Gửi kết quả công cụ lại cho mô hình để tiếp tục vòng lặp
return self.run(functions_response_parts)
return response
# Khởi tạo Agent với phong cách của Linus Torvalds
agent = Agent(
model="gemini-3-pro-preview",
tools=file_tools,
system_instruction="Bạn là một Trợ lý Lập trình đắc lực. Hãy trả lời như thể bạn là Linus Torvalds."
)
response = agent.run(
contents="Bạn có thể liệt kê các tệp tin trong thư mục hiện tại của tôi không?"
)
print(response.text)
# Đầu ra mô phỏng:
# [Function Call] id=None args={'directory_path': '.'} name='list_dir'
# [Function Response] {'result': ['.venv', 'LICENSE', 'main.py' ]}
# "Đấy. Thư mục hiện tại của bạn gồm có: `LICENSE`, `main.py`..."
Chúc mừng! Bạn vừa xây dựng xong agent hoạt động đầu tiên của mình.
Giai đoạn 4: CLI Agent đa lượt hội thoại
Giờ đây, chúng ta có thể chạy agent của mình trong một vòng lặp CLI đơn giản. Chỉ cần một lượng mã nguồn ít ỏi đến kinh ngạc, chúng ta đã tạo ra được một hành vi cực kỳ mạnh mẽ.
# ... Mã nguồn cho lớp Agent, công cụ và định nghĩa từ Bước 3 nằm ở đây ...
agent = Agent(
model="gemini-3-pro-preview",
tools=file_tools,
system_instruction="Bạn là một Trợ lý Lập trình đắc lực. Hãy trả lời như thể bạn là Linus Torvalds."
)
print("Agent đã sẵn sàng. Hãy yêu cầu nó kiểm tra các tệp trong thư mục này.")
while True:
user_input = input("Bạn: ")
if user_input.lower() in ['exit', 'quit', 'thoát']:
break
response = agent.run(user_input)
print(f"Linus: {response.text}n")
Các thực hành tốt nhất để phát triển Agent
Xây dựng vòng lặp thì dễ; làm cho nó trở nên đáng tin cậy, minh bạch và có thể kiểm soát mới là việc khó. Dưới đây là các nguyên tắc kỹ thuật chính được đúc kết từ các thực tiễn hàng đầu trong ngành, được nhóm theo từng khu vực chức năng.
1. Định nghĩa Công cụ & Công thái học (Tool Definition & Ergonomics)
- Các công cụ chính là giao diện để mô hình tương tác với thế giới. Đừng chỉ bao bọc (wrap) các API nội bộ sẵn có của bạn. Nếu một công cụ gây bối rối cho con người, nó cũng sẽ gây bối rối cho mô hình:
- Đặt tên rõ ràng: Sử dụng các tên hiển nhiên như
search_customer_databasethay vìcust_db_v2_query. - Mô tả chính xác: Gemini đọc các chú thích hàm (docstrings) để hiểu khi nào và làm thế nào để dùng công cụ. Hãy dành thời gian viết chúng thật cẩn thận; về cơ bản, đây chính là "Prompt Engineering" dành cho công cụ.
- Trả về lỗi có ý nghĩa: Đừng trả về một đoạn mã lỗi Java dài 50 dòng. Nếu công cụ thất bại, hãy trả về một chuỗi ký tự rõ ràng như: Lỗi:
Không tìm thấy tệp. Có phải ý bạn là 'data.csv'?. Điều này cho phép agent tự sửa lỗi. - Chấp nhận đầu vào "mờ" (Fuzzy Inputs): Nếu mô hình thường xuyên đoán sai đường dẫn tệp, hãy cập nhật công cụ của bạn để xử lý các đường dẫn tương đối hoặc đầu vào không chính xác thay vì chỉ báo lỗi đơn thuần.
2. Kỹ nghệ Ngữ cảnh (Context Engineering)
Các mô hình có một "ngân sách chú ý" hữu hạn. Việc quản lý thông tin nào được đưa vào ngữ cảnh là yếu tố then chốt cho cả hiệu suất và chi phí.
- Đừng "đổ" dữ liệu bừa bãi: Đừng tạo ra một công cụ trả về toàn bộ bảng dữ liệu 10MB. Thay vì
get_all_users(), hãy tạosearch_users(query: str). - Tải dữ liệu đúng lúc (Just-in-time Loading): Thay vì tải trước tất cả dữ liệu (theo cách RAG truyền thống), hãy sử dụng chiến lược "vừa kịp lúc". Agent nên duy trì các mã định danh nhẹ (đường dẫn tệp, ID) và dùng công cụ để tải nội dung một cách linh hoạt chỉ khi cần thiết.
- Nén dữ liệu: Đối với các agent chạy trong thời gian dài, hãy tóm tắt lịch sử, xóa bỏ ngữ cảnh cũ hoặc bắt đầu các phiên làm việc mới.
- Bộ nhớ Agentic: Cho phép agent duy trì các ghi chú hoặc bản nháp (scratchpad) được lưu trữ bên ngoài cửa sổ ngữ cảnh, và chỉ lấy lại chúng khi có liên quan.
3. Đừng quá phức tạp hóa (Don't over engineer)
Việc xây dựng các hệ thống đa agent (multi-agent) phức tạp rất hấp dẫn, nhưng đừng vội vàng.
- Tối ưu hóa một Agent đơn lẻ trước: Đừng ngay lập tức xây dựng hệ thống đa agent. Gemini cực kỳ mạnh mẽ trong việc xử lý hàng chục công cụ chỉ trong một câu lệnh duy nhất.
- Cơ chế thoát (Escape Hatches): Đảm bảo các vòng lặp có thể dừng lại bằng một điểm ngắt max_iterations (ví dụ: tối đa 15 lượt gọi).
- Rào chắn và Chỉ dẫn hệ thống: Sử dụng system_instruction để hướng dẫn mô hình bằng các quy tắc cứng (ví dụ: "Nghiêm cấm hoàn tiền quá $50") hoặc sử dụng các bộ phân loại bên ngoài.
- Sự tham gia của con người (Human-in-the-loop): Đối với các hành động nhạy cảm (như send_email hoặc execute_code), hãy tạm dừng vòng lặp và yêu cầu người dùng xác nhận trước khi công cụ thực sự được thực thi.
- Ưu tiên tính minh bạch và Gỡ lỗi: Ghi lại nhật ký (log) các lần gọi công cụ và tham số. Việc phân tích suy luận của mô hình sẽ giúp xác định vấn đề và cải thiện agent theo thời gian.
Kết luận
Xây dựng một agent không còn là phép màu nữa; đó là một nhiệm vụ kỹ thuật thực tế. Như chúng ta đã thấy, bạn có thể xây dựng một bản mẫu hoạt động tốt chỉ với chưa đầy 100 dòng mã.
Mặc dù việc hiểu các nguyên lý cơ bản này là chìa khóa, nhưng đừng sa đà vào việc thiết kế lại cùng một mô hình lặp đi lặp lại. Cộng đồng AI đã tạo ra những thư viện mã nguồn mở tuyệt vời giúp bạn xây dựng các agent phức tạp và mạnh mẽ hơn một cách nhanh chóng.
Nguồn bài viết từ Tác giả Phil Schmid