Khóa học
GLM-5-Turbo được thiết kế đặc biệt cho các quy trình kiểu agent, nơi mô hình điều phối việc thực thi nhiều bước. Mô hình được tối ưu để hiểu chỉ dẫn phức tạp, gọi công cụ một cách đáng tin cậy và tạo đầu ra có cấu trúc để hệ thống có thể tiêu thụ.
Trong hướng dẫn này, chúng ta sẽ xây dựng một agent trình duyệt thời gian thực có khả năng duyệt web chủ động, trích xuất thông tin liên quan và tạo bản tóm tắt gọn gàng cho người dùng. Kết thúc hướng dẫn, bạn sẽ có một kiến trúc tái sử dụng được mà bạn có thể mở rộng vượt ra ngoài lĩnh vực chuyến bay cho bất kỳ tác vụ tìm kiếm và trích xuất trong thế giới thực nào.
Bạn có thể xem dự án GLM-5 Turbo của chúng tôi hoạt động trong video bên dưới:
GLM-5-Turbo là gì?
GLM-5-Turbo được thiết kế để hỗ trợ quy trình agent trong thế giới thực. Nó cung cấp nhiều chế độ tư duy, cho phép người dùng cân bằng giữa phản hồi nhanh và suy luận sâu tùy theo tác vụ.
Đối với các ứng dụng tương tác, nó cũng hỗ trợ phát luồng đầu ra, cho phép phản hồi được gửi theo từng token để mang lại trải nghiệm mượt mà hơn cho người dùng.
Một trong những điểm mạnh cốt lõi là function calling, cho phép mô hình gọi các công cụ bên ngoài một cách đáng tin cậy. Mô hình cũng hỗ trợ đầu ra có cấu trúc như JSON, giúp tích hợp dễ dàng hơn với các hệ thống hạ nguồn và lớp giao diện người dùng.
Đối với quy trình dài, GLM-5-Turbo có caching ngữ cảnh, giúp duy trì hiệu năng trong những tương tác kéo dài mà không cần xử lý lặp đi lặp lại cùng thông tin. Nó cũng hỗ trợ tích hợp với hệ thống bên ngoài qua MCP (Model Context Protocol), cho phép mô hình kết nối với các công cụ và nguồn dữ liệu bổ sung.
Xây dựng cho quy trình agent
GLM-5-Turbo được tối ưu sâu cho môi trường kiểu agent, đặc biệt là các kịch bản như OpenClaw, nơi tác vụ bao gồm nhiều bước, nhiều công cụ và chuỗi thực thi dài.
Tối ưu hóa này cải thiện một số khía cạnh quan trọng:
- Độ tin cậy khi gọi công cụ: Đảm bảo mô hình có thể liên tục gọi đúng hàm.
- Phân rã chỉ dẫn: Cho phép mô hình chia nhỏ tác vụ phức tạp thành các bước nhỏ hơn.
- Ổn định trong chuỗi thực thi dài: Điều này thiết yếu cho agent nhiều bước.
Trong thực tế, GLM-5-Turbo đủ nhanh cho hệ thống tương tác nhưng vẫn đáng tin cậy trong các kịch bản nhiều bước và gọi công cụ — đúng như những gì chúng ta cần để xây dựng agent trình duyệt.
Dự án Ví dụ với GLM-5-Turbo: Xây dựng Agent Trình duyệt Thời gian thực
Trong hướng dẫn này, chúng ta sẽ tạo một ứng dụng Streamlit hoạt động như trợ lý du lịch gọn nhẹ. Người dùng đưa ra truy vấn như “Tìm chuyến bay rẻ nhất từ SFO đến JFK từ thứ Sáu tới thứ Hai.” Hệ thống sẽ:
- Tìm kiếm web các trang kết quả chuyến bay phù hợp
- Mở và kiểm tra các trang đó
- Trích xuất thông tin hữu ích như giá, thời lượng và số điểm dừng
- Tóm tắt các lựa chọn tốt nhất theo định dạng thân thiện với người dùng
Ở mức tổng quan, hệ thống gồm bốn thành phần phối hợp với nhau.
- Lớp LLM đầu tiên (
llm_client.py), gửi yêu cầu chat-completion tới Z.AI hoặc OpenRouter. - Lớp công cụ trình duyệt thứ hai (
browser_tools.py), cung cấp các hành động như tìm kiếm, mở trang và trích xuất nội dung. - Vòng lặp agent thứ ba (
agent.py), kết nối mô hình và công cụ thành quy trình nhiều bước. - Cuối cùng, giao diện Streamlit (
app.py) cung cấp giao diện tương tác và trực quan hóa.
Mã nguồn đầy đủ: https://github.com/AashiDutt/GLM5_TURBO
Bước 1: Điều kiện tiên quyết
Trước khi xây dựng ứng dụng, hãy đảm bảo bạn có các yếu tố sau:
- Python 3.9+
- Khóa API GLM-5-Turbo (Z.AI hoặc OpenRouter)
- Đã cài đặt Playwright để tự động hóa trình duyệt
Cài đặt các thư viện cần thiết:
pip install streamlit pandas python-dotenv requests playwright
playwright install chromium
Tệp app.py dùng Streamlit, pandas, dotenv và các mô-đun tùy chỉnh. Trong khi đó, tệp browser_tools.py dùng requests và Playwright. Các cuộc gọi API được thực hiện trong llm_client.py bằng requests.
Giờ hãy chạy lệnh sau để tạo tệp .env nơi chúng ta sẽ đặt khóa API:
cp .env.example .env
Chọn endpoint API ưa thích và truyền khóa API:
.env
PROVIDER=zai
# Direct Z.AI API
ZAI_API_KEY=Your Z.AI API key
ZAI_BASE_URL=https://api.z.ai/api/paas/v4/chat/completions
ZAI_MODEL=glm-5-turbo
# OpenRouter
OPENROUTER_API_KEY=Your OpenRouter API key
OPENROUTER_BASE_URL=https://openrouter.ai/api/v1/chat/completions
OPENROUTER_MODEL=z-ai/glm-5-turbo
# Browser
HEADLESS=true
MAX_STEPS=10
Tương tự, Playwright browser được khởi chạy từ constructor này trong browser_tools.py:
class BrowserTools:
def __init__(self, headless: bool = True) -> None:
self.playwright = sync_playwright().start()
self.browser = self.playwright.chromium.launch(headless=headless)
self.context = self.browser.new_context(viewport={"width": 1440, "height": 1100})
self.page = self.context.new_page()
Khối này khởi tạo một môi trường trình duyệt được kiểm soát hoàn toàn bằng Playwright, mà agent dùng để tương tác với web.
Nó khởi động engine Playwright, mở trình duyệt Chromium, tạo một phiên trình duyệt cách ly và mở một trang mới nơi mọi thao tác diễn ra.
Thiết lập này cho phép agent duyệt các website thực, quan sát nội dung trực tiếp và ra quyết định dựa trên những gì nó thấy.
Bước 2: Thiết lập client LLM
Tiếp theo, chúng ta sẽ thiết lập LLMClient hỗ trợ cả Z.AI trực tiếp lẫn OpenRouter với cùng một giao diện. Endpoint theo nhà cung cấp được chọn trong constructor. Vì mức giá tương đương, bạn có thể dùng API nào tùy thích cho bản demo này.
Thiết lập chính như sau:
class LLMClient:
def __init__(
self,
provider: str,
api_key: str,
model: str,
app_name: str = "GLM-5 Browser Agent Demo",
app_url: str = "http://localhost:8501",
) -> None:
self.provider = provider.lower().strip()
self.api_key = api_key
self.model = model
if self.provider == "zai":
self.url = os.getenv("ZAI_BASE_URL", "https://api.z.ai/api/paas/v4/chat/completions")
elif self.provider == "openrouter":
self.url = os.getenv("OPENROUTER_BASE_URL", "https://openrouter.ai/api/v1/chat/completions")
Hàm khởi tạo này cấu hình LLM client bằng cách chọn endpoint API phù hợp (Z.AI hoặc OpenRouter) trong khi vẫn giữ giao diện nhất quán.
Nó lưu trữ khóa API, mô hình và URL endpoint, cho phép phần còn lại của hệ thống gọi mô hình mà không phải bận tâm đến khác biệt theo nhà cung cấp.
Sau đó create_chat_completion() dựng payload, đính kèm công cụ khi cần và phân tích bất kỳ lệnh gọi công cụ nào được trả về.
payload: Dict[str, Any] = {
"model": self.model,
"messages": messages,
"temperature": temperature,
"max_tokens": max_tokens,
"stream": False,
}
if tools:
payload["tools"] = tools
payload["tool_choice"] = "auto"
Payload trên xác định yêu cầu gửi tới mô hình, bao gồm ngữ cảnh hội thoại và tham số sinh. Bằng cách đính kèm tools và bật tool_choice="auto", mô hình có thể tự quyết định khi nào gọi hàm bên ngoài.
Bước 3: Định nghĩa công cụ trình duyệt
Trong agent.py, chúng ta định nghĩa schema công cụ mà mô hình có thể gọi. Bao gồm search_web, open_url, open_search_result, extract_page, extract_flight_cards, click, type_text và wait_for_text.
Đây là một ví dụ định nghĩa công cụ:
{
"type": "function",
"function": {
"name": "search_web",
"description": "Search the public web for flight result pages or travel aggregators.",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string"},
"max_results": {"type": "integer", "default": 8},
},
"required": ["query"],
},
},
}
Schema này cho mô hình biết công cụ làm gì, đầu vào mong đợi và cách gọi. Bằng cách định nghĩa công cụ theo cách này, chúng ta cho phép mô hình tự động quyết định khi nào tìm kiếm web thay vì mã hóa cứng luồng xử lý.
Ở phía thực thi, các công cụ đó được hiện thực trong browser_tools.py. Với bản demo này, chúng tôi dùng endpoint HTML của DuckDuckGo để lưu các liên kết kết quả hiện tại cho các bước điều hướng tiếp theo.
def search_web(self, query: str, max_results: int = 8) -> Dict[str, Any]:
url = f"https://html.duckduckgo.com/html/?q={quote_plus(query)}"
headers = {"User-Agent": "Mozilla/5.0"}
resp = requests.get(url, headers=headers, timeout=30)
resp.raise_for_status()
html = resp.text
Hàm search_web() trên thực hiện tìm kiếm web nhẹ bằng cách lấy HTML thô của kết quả và phân tích, tránh cần tới API tìm kiếm trả phí. Nó cũng lưu kết quả nội bộ để agent có thể điều hướng trong các bước sau.
Giờ khi đã định nghĩa cách agent tương tác với web, bước tiếp theo là xem cách nó thực sự dùng các công cụ này để trích xuất thông tin chuyến bay qua tìm kiếm web.
Bước 4: Trích xuất thông tin chuyến bay
Khi mô hình tìm thấy nguồn hứa hẹn, nó cần kiểm tra. Chúng ta có thể dùng các công cụ trình duyệt (từ bước 3) để trích xuất ảnh chụp trang thô.
Bước 4.1: Mở và trích xuất trang
Chúng ta sẽ dùng công cụ open_url() để tải trang web và trả về ảnh chụp với tiêu đề, URL và văn bản hiển thị.
def open_url(self, url: str) -> Dict[str, Any]:
self.page.goto(url, wait_until="domcontentloaded", timeout=45000)
self.page.wait_for_timeout(1200)
return self._page_snapshot()
Để trích xuất rộng hơn, chúng ta sẽ dùng công cụ extract_page() sau đây, trả về ảnh chụp trang và tập liên kết thu thập từ trang hiện tại.
def extract_page(self, max_chars: int = 7000, link_limit: int = 20) -> Dict[str, Any]:
snapshot = self._page_snapshot(max_chars=max_chars)
snapshot["links"] = self._collect_links(limit=link_limit)
self.current_links = snapshot["links"]
return snapshot
Điều này hữu ích vì mô hình không phải lúc nào cũng cần nhấp bừa. Đôi khi nó có thể quan sát văn bản trang hiện tại và các liên kết sẵn có trước, rồi mới quyết định bước tiếp theo.
Bước 4.2: Trích xuất chuyến bay theo heuristic
Phần then chốt của demo là hàm extract_flight_cards() nhận văn bản trang hiển thị và dùng regex để phát hiện giá, thời gian, thời lượng và số điểm dừng.
Đây là cốt lõi của logic đó:
money_pattern = re.compile(r"(?:\$|USD\s?)(\d{2,5})")
time_pattern = re.compile(r"\b\d{1,2}:\d{2}\s?(?:AM|PM|am|pm)?\b")
duration_pattern = re.compile(r"\b\d{1,2}\s?h\s?\d{0,2}\s?m?\b|\b\d{1,2}\s?hr\s?\d{0,2}\s?min?\b", re.I)
stop_pattern = re.compile(r"nonstop|1 stop|2 stops|3 stops", re.I)
Các regex này hoạt động như bộ dò cho tín hiệu liên quan tới chuyến bay. money_pattern bắt giá ở định dạng như $299 hoặc USD 299, còn time_pattern nhận diện thời gian khởi hành và đến nơi.
Tương tự, duration_pattern trích xuất thời lượng di chuyển, và stop_pattern phát hiện chuyến bay bay thẳng hay có quá cảnh. Bạn có thể xây dựng mẫu tương tự cho bất kỳ trường hợp sử dụng nào.
Khi các tín hiệu này đã được định nghĩa, hàm sẽ quét các đoạn văn bản nhỏ và tìm tổ hợp có ý nghĩa. Khi có khớp, nó tạo một thẻ chuyến bay có cấu trúc:
card = {
"snippet": joined[:350],
"price_usd": int(price_match.group(1)),
"times": times[:2],
"duration": duration.group(0) if duration else None,
"stops": stops.group(0) if stops else None,
}
Khối mã trên chuyển văn bản thô thành biểu diễn có cấu trúc của một lựa chọn chuyến bay. Nó trích xuất giá, giới hạn mục thời gian ở những mục liên quan nhất và xử lý trường hợp thiếu trường. Cách này giữ nguyên ngữ cảnh gốc, hữu ích khi gỡ lỗi.
Với cơ chế trích xuất này, agent đã có cách chuyển nội dung trang không cấu trúc thành dữ liệu dùng được. Tiếp theo, chúng ta sẽ xem dữ liệu này đi qua vòng lặp agent như thế nào để dẫn dắt quyết định nhiều bước.
Bước 5: Vòng lặp agent
Hành vi thực tế của agent nằm trong lớp BrowserAgent (bên trong agent.py). Lớp này gửi prompt và schema công cụ tới mô hình, kiểm tra xem mô hình có muốn gọi công cụ không, thực thi công cụ đó, bổ sung kết quả công cụ trở lại vào hội thoại và tiếp tục cho đến khi tạo ra câu trả lời cuối.
Đây là vòng lặp chính:
for step in range(1, self.max_steps + 1):
assistant = self.llm.create_chat_completion(
messages=messages,
tools=TOOLS,
temperature=0.2,
max_tokens=2200,
enable_thinking=False,
)
if assistant.tool_calls:
...
result = self.tool_runner(tc.name, tc.arguments)
...
continue
final_content = assistant.content.strip()
if final_content:
yield {"type": "final", "step": step, "content": final_content}
return
Vòng lặp này là trái tim của bản demo.
Ở mỗi bước, mô hình nhận trạng thái hội thoại hiện tại và quyết định bước tiếp theo. Nếu trả về lệnh gọi công cụ, các lệnh đó được thực thi bằng tool_runner, và kết quả được nạp lại vào vòng lặp cho lần lặp kế tiếp.
Quy trình tiếp tục cho đến khi mô hình đưa ra phản hồi cuối cùng thay vì gọi công cụ.
Vòng lặp cũng áp đặt giới hạn số bước tối đa để tránh chạy vô hạn, giúp quá trình được kiểm soát và hiệu quả.
Bước 6: Kết nối công cụ với ứng dụng
Trong app.py, chúng ta ánh xạ tên công cụ do mô hình yêu cầu tới các phương thức trình duyệt thực tế. Ánh xạ này được triển khai bằng hàm make_tool_runner():
def make_tool_runner(browser: BrowserTools):
def run(name: str, args: Dict[str, Any]):
if name == "search_web":
return browser.search_web(**args)
if name == "open_url":
return browser.open_url(**args)
if name == "open_search_result":
return browser.open_search_result(**args)
if name == "extract_page":
return browser.extract_page(**args)
if name == "extract_flight_cards":
return browser.extract_flight_cards(**args)
Hàm này đóng vai trò bộ phân phối giữa mô hình và trình duyệt. Mô hình chỉ trả về tên công cụ và tham số, và run() chuyển điều đó thành lời gọi hàm trên thể hiện BrowserTools.
Giờ khi agent đã có thể gọi và thực thi công cụ thành công, bước tiếp theo là xây dựng giao diện người dùng kết nối mọi thứ với nhau.
Bước 7: Xây dựng giao diện Streamlit
Giao diện Streamlit biến agent trình duyệt thành một ứng dụng tương tác. Nó thu thập chi tiết chuyến đi từ người dùng, khởi chạy agent, phát trực tiếp các bước trung gian và cuối cùng hiển thị bản tóm tắt cùng bảng có cấu trúc các lựa chọn chuyến bay hàng đầu. Toàn bộ luồng UI này được triển khai trong app.py.
st.set_page_config(page_title="GLM-5 Browser Agent", page_icon="", layout="wide")
st.title("GLM-5-Turbo Real-Time Browser Agent")
st.subheader("Trip Request")
origin = st.text_input("Origin", value="SFO")
destination = st.text_input("Destination", value="JFK")
depart_date = st.date_input("Depart date")
return_date = st.date_input("Return date")
adults = st.number_input("Adults", min_value=1, max_value=9, value=1)
natural_request = st.text_area(
"Optional natural language request",
value="Find the cheapest reasonable flight options and summarize the best ones.",
height=120,
)
user_query = (
f"Find flight options from {origin} to {destination} for {adults} adult(s). "
f"Departure date: {depart_date}. Return date: {return_date}. "
f"User preference: {natural_request}"
)
run = st.button("Run browser agent", type="primary", use_container_width=True)
if run:
if not api_key:
st.error("Please enter an API key in the sidebar.")
st.stop()
llm = LLMClient(provider=provider, api_key=api_key, model=model)
event_box = st.container(border=True)
result_box = st.container(border=True)
all_events = []
with managed_browser(headless=headless) as browser:
agent = BrowserAgent(llm=llm, tool_runner=make_tool_runner(browser), max_steps=max_steps)
for event in agent.stream(user_query):
all_events.append(event)
with event_box:
if event["type"] == "status":
st.write(f"**Step {event['step']}** — {event['content']}")
elif event["type"] == "tool_call":
with st.expander(f"Step {event['step']} • Tool call: {event['tool']}", expanded=False):
st.json(event["arguments"])
elif event["type"] == "tool_result":
with st.expander(f"Step {event['step']} • Tool result: {event['tool']}", expanded=False):
st.json(event["result"])
elif event["type"] == "final":
st.success("Agent finished.")
final_event = next((e for e in reversed(all_events) if e["type"] == "final"), None)
final_text = final_event["content"] if final_event else ""
with result_box:
st.subheader("Final Answer")
parsed = parse_final_json(final_text)
if parsed:
st.markdown(render_final_markdown(parsed))
options_df = build_options_dataframe(parsed)
if not options_df.empty:
st.subheader("Top options table")
st.dataframe(
options_df,
use_container_width=True,
column_config={
"Link": st.column_config.LinkColumn("Link", display_text="Open source"),
},
)
else:
st.markdown(final_text)
Dưới đây là cách giao diện Streamlit kết nối tất cả thành phần:
- Lệnh
st.set_page_config()vàst.title()xác định bố cục tổng thể và tiêu đề ứng dụng, trong khi các widget nhập liệu nhưst.text_input(),st.date_input(),st.number_input()vàst.text_area()thu thập chi tiết chuyến đi. - Khi người dùng nhấp nút
Run browser agent, ứng dụng đầu tiên kiểm tra xem có khóa API chưa, sau đó khởi tạoLLMClient. Ứng dụng tạo các vùng chứa riêng cho log trung gian của agent và kết quả cuối, rồi mở phiên trình duyệt Playwright được quản lý. Bên trong phiên đó,BrowserAgentbắt đầu phát sự kiện, và UI hiển thị từng cập nhật trạng thái, lệnh gọi công cụ và kết quả công cụ. - Khi agent hoàn tất, ứng dụng trích xuất sự kiện cuối, phân tích JSON trả về và hiển thị. Bản tóm tắt được hiển thị qua
render_final_markdown(), trong khibuild_options_dataframe()chuyển các lựa chọn chuyến bay có cấu trúc thành bảng với liên kết nguồn có thể nhấp.
Hoàn thành bước này, bạn có thể lưu tất cả dưới tên app.py và khởi chạy ứng dụng bằng:
streamlit run app.py

Dự án này phù hợp làm bản demo thực tế vì nó dùng kết quả tìm kiếm trình duyệt thực và cơ chế gọi công cụ. Đồng thời, nó vẫn là nguyên mẫu. Website về chuyến bay có tính động cao, trang có thể nhiễu và một số nền tảng dùng biện pháp chống bot.
Logic trích xuất hiện tại cố tình mang tính heuristic, giúp hệ thống đơn giản và dễ hiểu.
Quan trọng hơn, thiết lập này không giới hạn ở chuyến bay. Chỉ với vài tinh chỉnh, chủ yếu là cập nhật prompt và logic trích xuất, bạn có thể tái sử dụng cùng kiến trúc để xây dựng agent cho mua sắm, nghiên cứu, tổng hợp tin tức hoặc bất kỳ tác vụ nào cần duyệt và tóm tắt thông tin trực tiếp.
Kết luận
Trong hướng dẫn này, chúng ta đã xây dựng một agent trình duyệt thời gian thực với GLM-5-Turbo, Playwright và Streamlit. Mô hình đóng vai trò lập kế hoạch, công cụ trình duyệt là lớp thực thi, còn UI chuyển kết quả có cấu trúc thành định dạng thân thiện với người dùng.
Điều quan trọng nhất là giá trị không chỉ đến từ mô hình. Nó đến từ việc kết hợp mô hình với lớp công cụ được kiểm soát và vòng lặp agent.
Tuy nhiên, có nhiều hướng mở rộng cho dự án này. Bạn có thể cải thiện trích xuất bằng cách thêm bộ phân tích theo từng trang, mở rộng hệ thống sang khách sạn hoặc lịch trình, hoặc tích hợp sở thích người dùng như chuyến bay thẳng hay hãng ưa thích. Những điều này có thể giúp bạn biến bản demo thành tiện ích trình duyệt hoàn chỉnh.
Câu hỏi thường gặp về GLM-5-Turbo
Tôi có cần GPU để chạy dự án GLM-5-Turbo này không?
Không. Dự án này không yêu cầu GPU vì mô hình được truy cập qua API (Z.AI hoặc OpenRouter). Mọi tính toán nặng diễn ra trên hạ tầng của nhà cung cấp, trong khi máy cục bộ của bạn chỉ xử lý UI và tự động hóa trình duyệt.
Tại sao dùng browser agent thay vì API chuyến bay?
API đáng tin cậy và có cấu trúc hơn, nhưng bị giới hạn ở dữ liệu được định nghĩa trước và thường yêu cầu trả phí. Ngược lại, một browser agent có thể:
- khám phá linh hoạt nhiều nguồn
- thích ứng với các truy vấn khác nhau
- hoạt động mà không cần API tích hợp sẵn
Tuy nhiên, với hệ thống sản xuất, cách tiếp cận lai (agent và API) thường là lựa chọn tốt nhất.
Vì sao agent đôi khi thất bại trên một số website?
Nhiều website du lịch sử dụng:
- render JavaScript động
- các biện pháp chống bot
- bố cục thay đổi thường xuyên
Vì bản demo này dựa vào tự động hóa trình duyệt tổng quát và trích xuất theo heuristic, nó có thể không hoạt động ổn định trên mọi trang.
Tại sao chúng ta tránh trả về JSON làm đầu ra cuối?
JSON hữu ích nội bộ cho gỡ lỗi, xử lý có cấu trúc, xếp hạng và lọc. Tuy nhiên, người dùng cuối thích bản tóm tắt dễ đọc. Đó là lý do hệ thống chuyển đổi đầu ra có cấu trúc thành định dạng thân thiện với người dùng.
Tôi là Chuyên gia Google Developers trong lĩnh vực ML (Gen AI), Chuyên gia Kaggle 3x và Đại sứ Women Techmakers với hơn 3 năm kinh nghiệm trong ngành công nghệ. Tôi đồng sáng lập một startup công nghệ y tế vào năm 2020 và hiện đang theo học thạc sĩ khoa học máy tính tại Georgia Tech, chuyên sâu về học máy.
