跳至内容

GLM-5-Turbo 教程:构建实时浏览器智能体

学习使用 GLM-5-Turbo 构建一个基于浏览器的 AI 智能体,搜索网页、提取实时航班数据,并返回清晰的推荐。
已更新 2026年10月6日  · 9分钟 阅读

使用 AI 探索

ChatGPTClaudePerplexity

GLM-5-Turbo 专为智能体式工作流设计,模型能够协调多步执行。它针对理解复杂指令、可靠调用工具以及生成可被系统消费的结构化输出做了优化。 

在本教程中,我们将构建一个实时浏览器智能体:主动浏览网页、提取相关信息,并为用户生成干净明了的摘要。完成后,您将获得一套可复用的架构,不仅适用于航班,还可扩展到任何真实世界的搜索与抽取任务。

您可以在下方视频中看到我们的 GLM-5 Turbo 项目演示:

什么是 GLM-5-Turbo?

GLM-5-Turbo 旨在支持真实场景下的智能体工作流。它提供多种思考模式,用户可根据任务在更快响应与更深层推理之间进行权衡。 

对于交互式应用,它还支持流式输出,使响应能以逐 token 传输,带来更灵敏的用户体验。

其核心优势之一是函数调用,模型可以可靠地调用外部工具。模型还支持 JSON 等结构化输出,便于与下游系统和 UI 层集成。

对于较长的工作流,GLM-5-Turbo 提供上下文缓存,避免重复处理相同信息,从而在长时交互中保持性能。它也支持通过 MCP(Model Context Protocol)与外部系统集成,让模型连接更多工具与数据源。

为智能体工作流而建

GLM-5-Turbo 针对智能体环境做了深度优化,尤其适用于类似OpenClaw 的场景,任务涉及多步骤、多工具与长链路执行。

这种优化提升了以下关键能力:

  • 工具调用可靠性:确保模型能稳定调用正确的函数。
  • 指令分解:将复杂任务拆解为更小的步骤。
  • 长链路执行稳定性:对多步智能体至关重要。

在实践中,GLM-5-Turbo 既足够快以支持交互系统,又能在多步工具调用场景中保持可靠,正好契合构建浏览器智能体的需求。

GLM-5-Turbo 示例项目:构建实时浏览器智能体

在本教程中,我们将创建一个 Streamlit 应用,充当轻量级出行助手。用户输入诸如“查找下周五至周一从 SFO 到 JFK 的最便宜航班”之类的查询。系统将:

  • 在网页上搜索相关航班页面
  • 打开并检查这些页面
  • 提取价格、时长与经停等有用信息
  • 以用户友好的格式汇总最佳选项

从高层来看,系统由四个协同工作的组件组成。

  • 第一层是 LLM 层(llm_client.py),用于向 Z.AI 或 OpenRouter 发送对话补全请求。
  • 第二层是浏览器工具层(browser_tools.py),提供搜索、打开页面与提取内容等操作。
  • 第三层是智能体循环(agent.py),将模型与工具连接为多步工作流。
  • 最后是 Streamlit 界面(app.py),提供交互与可视化。

完整源码:https://github.com/AashiDutt/GLM5_TURBO

步骤 1:先决条件

在构建应用之前,请确保具备以下条件:

  • Python 3.9+
  • GLM-5-Turbo 的 API 密钥(Z.AI 或 OpenRouter)
  • 已安装用于浏览器自动化的 Playwright

安装所需库:

pip install streamlit pandas python-dotenv requests playwright
playwright install chromium

app.py 使用了 Streamlit、pandas、dotenv 和自定义模块;而 browser_tools.py 使用 requests 与 Playwright。API 调用通过 llm_client.py 中的 requests 发起。

现在,运行以下命令创建一个存放 API 密钥的 .env 文件:

cp .env.example .env

选择您偏好的 API 端点并填入 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

同样地,Playwright 浏览器在 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()

该代码块利用 Playwright 初始化一个完全可控的浏览器环境,供智能体与网页交互。

它会启动 Playwright 引擎、打开一个 Chromium 浏览器、创建隔离会话,并在新页面中执行所有操作。

通过该设置,智能体可浏览真实网站、观察实时内容,并基于所见作出决策。

步骤 2:设置 LLM 客户端

接下来,我们将设置支持直接使用 Z.AI 与 OpenRouter 的 LLMClient,二者共享同一接口。构造函数中会选择特定提供商的端点。由于价格相近,本演示您可任选其一。

关键设置如下:

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")

该初始化过程会选择合适的 API 端点(Z.AI 或 OpenRouter),同时保持接口一致。

它会保存 API 密钥、模型与端点 URL,使系统其余部分无需关心提供商差异即可调用模型。

随后,create_chat_completion() 会组装请求载荷,在需要时附带工具,并解析返回的工具调用。

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"

上述载荷定义了发送给模型的请求,包括对话上下文与生成参数。通过附加 tools 并启用 tool_choice="auto",模型可自行决定何时调用外部函数。

步骤 3:定义浏览器工具

在 agent.py 中,我们定义了模型可调用的工具模式。这些工具包括 search_web, open_url、open_search_result、extract_page、extract_flight_cards、click、type_text 与 wait_for_text。

以下是一个工具定义示例:

{
    "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"],
        },
    },
}

该模式告知模型工具的作用、期望的输入与调用方式。通过如此定义工具,我们无需硬编码流程,模型可在需要时动态发起网页搜索。

在执行端,这些工具实现在 browser_tools.py 中。本演示采用 DuckDuckGo 的 HTML 端点以存储当前结果链接,便于后续导航。

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

上述 search_web() 函数通过获取原始 HTML 并解析,实现轻量网页搜索,避免依赖付费搜索 API。它还会在内部存储结果,便于智能体在后续步骤中导航。

在明确了智能体如何与网页交互后,下一步是查看它如何通过网页搜索提取航班信息。

步骤 4:提取航班信息

一旦模型找到有潜力的来源,就需要进行检查。我们可以使用(步骤 3 的)浏览器工具来提取原始页面快照。

步骤 4.1:打开并提取页面

我们将使用 open_url() 工具加载网页,并返回包含标题、URL 与可见文本的快照。

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()

更广泛的提取可使用下方 extract_page() 工具,它会返回页面快照及当前页面收集到的一组链接。

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

这很有用,因为模型不必总是盲点乱点。它可以先检查当前页面文本与可用链接,再决定下一步。

步骤 4.2:启发式航班抽取

演示的关键部分是 extract_flight_cards() 函数,它基于可见页面文本并用正则表达式模式检测价格、时间、时长与经停。

核心逻辑如下:

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)

这些正则模式相当于常见航班信号的“探测器”。money_pattern 捕获如 $299 或 USD 299 的价格格式,而 time_pattern 识别出发与到达时间。 

类似地,duration_pattern 提取行程时长,stop_pattern 判断航班是否直飞或有中转。您也可以为任意用例构建相似模式。

定义这些信号后,函数会扫描小块文本并寻找有意义的组合。一旦匹配,就构建一个结构化的航班卡片:

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,
}

上述代码将原始文本转换为航班选项的结构化表示:抽取价格、将时间条目限制为最相关的两项,并妥善处理缺失字段。同时保留原始上下文,便于调试。

完成抽取后,智能体即可将非结构化页面内容转化为可用数据。接下来我们看看这些数据如何在智能体循环中流转,驱动多步决策。

步骤 5:智能体循环

实际的智能体行为封装在 BrowserAgent 类(位于 agent.py 内)中。该类将提示与工具模式发送给模型;判断模型是否要调用工具;执行该工具;将工具结果追加回对话;并持续迭代直至产生最终答案。

主循环如下:

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

该循环是演示的核心。

每一步,模型都会接收当前对话状态并决定下一步操作。若返回工具调用,则通过 tool_runner 执行,并将结果反馈到循环中进入下一轮。

该流程会持续,直到模型不再调用工具而是生成最终响应。

循环还会设置最大步数限制,防止无限执行,确保流程可控高效。

步骤 6:将工具接入应用

在 app.py 中,我们将模型请求的工具名称映射到实际的浏览器方法。该映射由 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)

该函数充当模型与浏览器之间的分发器。模型仅返回工具名与参数,run() 会将其翻译为对 BrowserTools 实例的函数调用。

在智能体可以成功调用并执行工具后,下一步是构建把所有部分串联起来的用户界面。

步骤 7:构建 Streamlit 界面

Streamlit 界面将浏览器智能体包装为交互式应用。它收集用户的行程细节、启动智能体、流式展示中间步骤,并最终渲染摘要及包含最佳航班选项的结构化表格。完整 UI 流程实现在 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)

下面说明 Streamlit 界面如何将各组件融合:

  • st.set_page_config() 与 st.title() 定义了应用整体布局与标题;st.text_input()、st.date_input()、st.number_input() 与 st.text_area() 等输入控件用于采集行程细节。
  • 当用户点击 Run browser agent 按钮,应用先检查 API 密钥是否可用,然后初始化 LLMClient。接着为中间日志与最终结果创建独立容器,并开启受管的 Playwright 浏览器会话。在该会话中,BrowserAgent 开始流式输出事件,UI 将逐条展示状态更新、工具调用与工具结果。
  • 智能体完成后,应用会提取最终事件,解析返回的 JSON 并进行渲染。摘要通过 render_final_markdown() 展示,而 build_options_dataframe() 则把结构化的航班选项转换为带可点击来源链接的表格。

完成该步骤后,您可以将全部保存为 app.py 并通过以下命令启动应用:

streamlit run app.py

该项目之所以适合作为真实演示,是因为它使用了实际的浏览器搜索结果与工具调用。同时,它仍是原型:航班网站动态性强、页面噪声多,且部分平台使用反爬虫保护。

当前抽取逻辑有意保持启发式,从而使系统简单易懂。

更重要的是,这套方案并不局限于航班。稍作调整(主要是更新提示词与抽取逻辑),您即可复用同一架构来构建购物、研究、新闻聚合或任何需要浏览并汇总实时信息的智能体。

结语

在本教程中,我们使用 GLM-5-Turbo、Playwright 与 Streamlit 构建了一个实时浏览器智能体。模型充当规划者,浏览器工具是执行层,而 UI 则将结构化结果转化为用户友好的呈现。

最重要的结论是,价值并非仅来自模型本身,而是来自将模型与受控工具层和智能体循环相结合。

此外,本项目还有多种可扩展方向:通过添加站点特定解析器改进抽取;扩展到酒店或行程;或融入用户偏好(如直飞或偏好航空公司)。这些都能帮助您将该演示升级为成熟的浏览器扩展。

GLM-5-Turbo 常见问答

运行这个 GLM-5-Turbo 项目需要 GPU 吗?

不需要。本项目无需 GPU,因为模型通过 API(Z.AI 或 OpenRouter)访问。所有重计算都在提供商的基础设施上完成,您的本地机器仅负责 UI 与浏览器自动化。

为什么使用浏览器智能体而不是航班 API?

API 更可靠且结构化,但受限于预定义数据,并且通常需要付费接入。相比之下,浏览器智能体可以:

  • 动态探索多种来源
  • 适应不同查询
  • 无需预先集成 API 也能工作

不过,在生产系统中,通常采用混合方案(智能体 + API)是最佳选择。

为什么智能体在某些网站上会失败?

许多旅游网站使用:

  • 动态 JavaScript 渲染
  • 反爬虫保护
  • 频繁变动的页面布局

由于本演示依赖通用浏览器自动化与启发式抽取,因此可能无法在所有网站上稳定工作。

为什么我们没有将 JSON 作为最终输出?

JSON 在内部用于调试、结构化处理、排序与筛选。然而终端用户更偏好可读性强的摘要,因此系统会将结构化输出转换为用户友好的呈现形式。


Aashi Dutt's photo
Author
Aashi Dutt
LinkedIn
Twitter

我是一名 Google Developers 机器学习(生成式 AI)领域的专家、Kaggle 三项专家,以及 Women Techmakers 大使,拥有 3 年以上的技术从业经验。2020 年我共同创办了一家健康科技初创公司,目前在佐治亚理工学院攻读计算机科学硕士,专攻机器学习。

主题
人工智能
AI 代理

Top DataCamp Courses

课程

使用 LangGraph 构建多智能体系统

2 小时 45 分钟
8.9K
通过在 LangGraph 框架中应用新兴的 agentic 设计模式,构建强大的多智能体系统。
查看详情Right Arrow
开始课程
查看更多Right Arrow