CrewAI + Browserbase 打造多智能体酒店预订系统:hotel-booking-crew 本地化实战解析

【免费下载链接】ai-engineering-hub In-depth tutorials on LLMs, RAGs and real-world AI agent applications. 【免费下载链接】ai-engineering-hub 项目地址: https://gitcode.com/GitHub_Trending/ai/ai-engineering-hub

本篇技术指南围绕开源仓库 ai-engineering-hub 中的 hotel-booking-crew 实战项目展开,讲解如何用 CrewAI 编排多智能体、以 Browserbase 无头浏览器作为 Agent 的网页工具、以本地推理(DeepSeek-R1 via Ollama)或云端 API 作为 LLM 后端,构建一个能够在真实预订网站上自动检索酒店、对比房型与报价的 Streamlit 应用。读完本文,你将掌握这套"多智能体 + 浏览器工具 + 流式 UI"的完整落地链路,并理解仓库中各源码文件(app.py、app_openai.py、browserbase.py、kayak.py)各自的职责与调用关系。

项目概览:一个本地优先的多智能体酒店搜索 Crew

hotel-booking-crew/README.md 将本项目的核心定位概括为:构建一个 100% 本地运行的多智能体酒店预订 Crew,目标是自动为用户找到性价比最高、最合适的酒店。它包含两条关键技术选型:

  • Browserbase:提供可远程访问的浏览器实例,项目基于它封装了一个"无头浏览器"工具,让 Agent 能真实打开并读取 Kayak 等酒店聚合站点的搜索结果;
  • CrewAI:负责多智能体的角色设定、任务编排与协作执行。

README 同时说明其模型接入意图:主程序 app.py 面向本地运行的 DeepSeek-R1(通过 Ollama 拉起),app_openai.py 则依赖 OpenAI API。这种"一个界面、两种模型后端"的设计,既保证了想完全离线运行时可行,也保留了使用云端模型快速验证的入口。

从当前仓库快照看,hotel-booking-crew 目录共包含以下文件,它们共同构成完整的可运行应用:

文件职责
hotel-booking-crew/README.md项目说明、依赖同步与环境变量指南
hotel-booking-crew/pyproject.toml依赖声明与版本约束(uv 项目元数据)
hotel-booking-crew/app.pyStreamlit 主界面 + CrewAI 编排(显式指定 LLM 模型)
hotel-booking-crew/app_openai.py同构的备用入口,走 CrewAI 默认 OpenAI 模型
hotel-booking-crew/browserbase.py基于 Playwright 封装的 Browserbase 浏览器加载工具
hotel-booking-crew/kayak.py生成 Kayak 酒店搜索 URL 的工具
hotel-booking-crew/uv.lock锁定依赖版本,保证可复现安装

多智能体编排流程:两个 Agent 与两个 Task

从 app.py 与 app_openai.py 的源码可以看到,应用定义了两个 CrewAI Agent 与两个 Task:

  • Hotels Agent(角色 "Hotels"):目标是"搜索酒店",绑定了 kayak_hotels 与 browserbase 两个工具,并设置 allow_delegation=False,不允许向其他 Agent 转交工作。它承担所有需要访问真实网页的检索动作。
  • Summarize Agent(角色 "Summarize"):目标是"汇总酒店信息与设施",不带任何工具,只负责将检索结果整理成结构清晰、便于用户阅读的文本。它也关闭了委派。

两个任务均挂在 Hotels Agent 上:

  1. search_task:根据用户输入的标准(如地点、日期、入住人数)搜索酒店,description 中通过模板变量注入动态信息:"Search hotels according to criteria {request}. Current year: {current_year}";
  2. search_booking_providers_task:加载酒店详情,找出可用的预订渠道及其价格,即执行比价。

这里值得留意的是 expected_output 字段。源码为每个任务都写死了"输出范例":

  • search_task 期望输出形如"纽约 9 月 21-22 日 Top 5 酒店",每条包含 Rating、Price、Location、Amenities 与 Booking 链接;
  • search_booking_providers_task 期望输出包含 Room Types、Price Range、Special Offers 及多个 Booking Options(Kayak / Hotels.com / 直订价)。

在 CrewAI 中,expected_output 会作为约束条件进入 LLM 的提示词,它本质上是格式规范器——用结构化范例引导模型输出固定字段,避免自由发挥导致结果无法解析。这也是把多 Agent 输出变得稳定可用的关键技巧。

两个任务按顺序流程(Crew 默认 Process)在 Agent 上依次执行:先由 Hotels Agent 用浏览器搜出候选酒店清单,再加载各家酒店详情与报价,最后交给 Summarize Agent 汇总为最终答复。Crew 实例的组装与执行在 app.py:

crew = Crew(
    agents=[hotels_agent, summarize_agent],
    tasks=[search_task, search_booking_providers_task],
    max_rpm=1,
    verbose=True,
    planning=True,
    llm=load_llm(),
)
result = crew.kickoff(
    inputs={
        "request": request,
        "current_year": datetime.date.today().year,
    }
)

其中 planning=True 会启用 CrewAI 的计划阶段,让 LLM 先规划任务执行顺序;max_rpm=1 将请求速率限制在每分钟 1 次,降低对模型的调用压力。两个文件在此存在参数差异:app.py 为 max_rpm=1,而 app_openai.py 为 max_rpm=100,且后者未在 Agent/Crew 层显式注入 LLM,改用 CrewAI 默认的 OpenAI 配置。

环境准备与依赖安装

README 给出的起步命令非常简洁——使用 uv 同步项目依赖:

uv sync

仓库根目录的 pyproject.toml 记录了完整依赖与约束:

[project]
name = "hotel-booking-crew"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
    "browserbase>=1.2.0",
    "crewai-tools>=0.38.1",
    "html2text>=2024.2.26",
    "playwright>=1.51.0",
    "python-dotenv>=1.1.0",
    "streamlit>=1.44.1",
]

各依赖在本项目中的实际用途如下:

依赖版本下限用途
streamlit1.44.1构建搜索表单、展示结果的 Web UI
crewai-tools0.38.1提供 @tool 装饰器,将函数包装为 Agent 可调用的工具
playwright1.51.0无头浏览器核心,负责连接 Browserbase 的远端 Chromium
browserbase1.2.0Browserbase SDK(作为会话相关的配套依赖声明)
html2text2024.2.26将浏览器抓取的 HTML 转为纯文本,方便 LLM 阅读
python-dotenv1.1.0从 .env 加载环境变量

需要说明:项目要求 Python >= 3.12,这是运行前的重要前提。uv.lock 已锁定全部传递依赖的具体版本,uv sync 后可获得一致的运行环境。

环境变量配置:BROWSERBASE_API_KEY 与可选的 OPENAI_API_KEY

README 明确列出了两类需要配置的环境变量:

BROWSERBASE_API_KEY=...        # 必填,browserbase.py 连接浏览器依赖它
OPENAI_API_KEY=...             # 仅运行 app_openai.py 时需要,本地运行可忽略
变量是否必需说明
BROWSERBASE_API_KEY必需用于 wss://connect.browserbase.com 的鉴权,代码在 browserbase.py 中直接以 os.environ["BROWSERBASE_API_KEY"] 读取
OPENAI_API_KEY仅 app_openai.py 需要app_openai.py 未显式指定 LLM,CrewAI 默认走 OpenAI 接口

README 建议以 .env.example 为参照创建自己的 .env 文件,再运行应用。需要说明的是:当前仓库快照的 hotel-booking-crew 目录中并未附带 .env.example 文件(目录内仅有 README、四个 .py、pyproject.toml 与 uv.lock),因此实际操作时只需自行按上表在项目目录下手动创建 .env 并填入 BROWSERBASE_API_KEY 即可。

两个应用文件都通过 load_dotenv()(app.py、app_openai.py)在启动时把 .env 内容加载进 os.environ。此外,app.py 还在 Streamlit 侧边栏提供了密码输入框,用户可以直接在界面上输入 Browserbase API Key,代码随即写入 os.environ["BROWSERBASE_API_KEY"](app.py),因此存在两条配置通道:环境变量文件,或运行时 UI 输入。

模型接入:本地 DeepSeek-R1 与云端 API 两种模式

README 描述的设计意图是 app.py 走本地 DeepSeek-R1(Ollama),而 app_openai.py 需要 OpenAI Key。从源码结构看,两个入口文件只有少量差异,可以交叉印证这种双后端设计:

  1. app_openai.py:Agent 与 Crew 均未指定 llm 参数,CrewAI 框架会使用默认的 OpenAI 模型配置,这正是它要求配置 OPENAI_API_KEY 的原因;
  2. app.py:通过 load_llm() 封装并缓存 LLM 实例(app.py):
@st.cache_data(show_spinner=False)
def load_llm():
    """Initialize and return Groq LLM with caching"""
    return LLM(model="groq/meta-llama/llama-4-scout-17b-16e-instruct")

需要如实指出一个仓库内部的不一致:README 声称 app.py 面向本地 Ollama DeepSeek-R1,但当前仓库快照中 app.py 的 load_llm() 实际上把模型字符串指向了 Groq 托管的 llama-4-scout-17b-16e-instruct,且 pyproject.toml 依赖中也未包含 ollama 相关依赖。因此,就当前代码而言,"100% 本地"取决于你如何配置这一模型入口。若希望与 README 描述保持一致、真正改成本地推理,需要做的核心改动点非常集中——只有 app.py 中 LLM(model=...) 这一处:将模型字符串替换为指向本地 Ollama 服务的标识(如 ollama/<模型名>,前提是已用 Ollama 拉取 DeepSeek-R1 模型),即可让整个 Crew 的 Agent 与规划过程都落到本地模型上,这也是该项目"本地化优先"设计意图的落点。

从工程角度,这种"把 LLM 实例抽成一个函数、并在 Agent 和 Crew 两级统一传入"的做法值得借鉴:它让模型选型成为一个单点可切换的配置项,Agent 逻辑本身与后端模型解耦,从而在同一套多智能体代码上快速实验本地模型与云端模型。

Agent 工具拆解:浏览器抓取与搜索 URL 构造

CrewAI 的 Agent 本身没有联网能力,全部"行动力"来自工具。本项目只有两个自定义工具,职责非常清晰。

Browserbase 无头浏览器工具(browserbase.py)

browserbase.py 定义了被两个 Agent 共用的核心工具,用于把"打开网页、读取结果"变成 LLM 可直接调用的函数:

@tool("Browserbase tool")
def browserbase(url: str):
    """
    Loads a URL using a headless webbrowser

    :param url: The URL to load
    :return: The text content of the page
    """
    with sync_playwright() as playwright:
        browser = playwright.chromium.connect_over_cdp(
            "wss://connect.browserbase.com?apiKey="
            + os.environ["BROWSERBASE_API_KEY"]
        )
        context = browser.contexts[0]
        page = context.pages[0]
        page.goto(url)

        # Wait for the flight search to finish
        sleep(25)

        content = html2text(page.content())
        browser.close()
        return content

实现要点分析:

  • 连接方式:使用 Playwright 的 connect_over_cdp 通过 WebSocket(wss://connect.browserbase.com + API Key)连接 Browserbase 托管的 Chromium,而不是本地拉起浏览器,因此无需在本地维护浏览器二进制与无头环境;
  • 等待策略:page.goto(url) 后 sleep(25) 硬编码等待约 25 秒,让目标站点的搜索结果完成渲染。从源码注释看,这段逻辑最初是为航班搜索("Wait for the flight search to finish")设计的,直接复用在酒店搜索上。由于是固定延时而非等待选择器,实测中如页面加载更慢可自行调大该值,这是后续可优化的点;
  • 文本化输出:调用 html2text(page.content()) 把页面 HTML 转成 Markdown 风格纯文本后返回——LLM 无法消化整段 HTML,但能高效阅读结构化文本,工具与模型的输入输出边界由此闭合;
  • 注册方式:函数外层用 crewai.tools 的 @tool("Browserbase tool") 装饰器把普通函数升级为 CrewAI 工具,工具名称与 docstring 会一并进入 Agent 的提示词,作为 LLM 决定"何时调用、传什么参数"的依据。

Kayak 搜索 URL 构造工具(kayak.py)

kayak.py 的工具逻辑更简单——它不抓取页面,而是把搜索参数拼成标准化的 Kayak 酒店搜索 URL,作为 browserbase 工具的前置输入:

@tool("Kayak Hotel Tool")
def kayak_hotel_search(
    location_query: str, check_in_date: str, check_out_date: str, num_adults: int = 2
) -> str:
    """
    Generates a Kayak URL for hotel searches based on location, dates, and number of adults.

    :param location_query: The location string used by Kayak (e.g., 'Hisar,Haryana,India-p15321')
    :param check_in_date: The check-in date in 'YYYY-MM-DD' format
    :param check_out_date: The check-out date in 'YYYY-MM-DD' format
    :param num_adults: The number of adults (defaults to 2)
    :return: The Kayak URL for the hotel search
    """
    URL = f"https://www.kayak.co.in/hotels/{location_query}/{check_in_date}/{check_out_date}/{num_adults}adults"
    return URL

可以看到 URL 模板的组成:/hotels/{地点}/{入住日期}/{退房日期}/{成人数}adults,地点需要符合 Kayak 内部编码(如 Hisar,Haryana,India-p15321 这种 城市,地区,国家-编码 形态,docstring 给出了示例),日期使用 YYYY-MM-DD。该工具导出的实例被重命名为 kayak_hotels(kayak_hotels = kayak_hotel_search),两个 app 文件中 Hotels Agent 的 tools=[kayak_hotels, browserbase] 引用的正是它。

典型调用链可以归纳为:LLM 先调用 kayak_hotels(地点, 日期, 成人数) 得到目标 URL → 再把该 URL 传给 browserbase(url) 让真实浏览器打开并抓取文本 → 抓取结果回流给 LLM 提炼成结构化酒店信息。两个工具一个负责"定位目标页面",一个负责"获取页面内容",职责互补。

从 Streamlit 表单到 Crew 执行:完整调用链

应用在 Streamlit 中提供了完整的酒店搜索表单(app.py):地点(文本输入)、成人数量(1-10 的数值输入,默认 2)、入住与退房日期(datetime.date 选择器,退房默认比入住晚一天)。

点击 "Search Hotels" 按钮后,进入点击回调执行逻辑,包含三层前置校验与一次 Crew 执行(app.py):

  1. 校验 API Key:os.environ 中没有 BROWSERBASE_API_KEY 时直接提示先到侧边栏输入;
  2. 校验日期合法性:check_out_date <= check_in_date 时报错"退房日期必须晚于入住日期";
  3. 构造请求文本:把表单字段拼装成一句自然语言指令—— "hotels in {地点} from {月 日} to {月 日} for {N} adults";
  4. 执行 Crew:以该请求为 inputs["request"]、以当年年份为 inputs["current_year"] 调用 crew.kickoff(...),任务模板中的 {request}、{current_year} 占位符在此被替换;
  5. 渲染结果:成功后 st.markdown(result) 将最终汇总展示在页面,异常则捕获并展示错误信息。

current_year 的注入是个细节:酒店价格与评分页面随时间变化,LLM 若不知道"现在"是哪一年,容易把陈旧常识当成实时信息。把当前年份作为任务上下文显式传入,是提示工程中对抗时间幻觉的实用手法。

若按 README 的指引运行:

streamlit run app.py

即启动 Streamlit 服务并在浏览器打开控制台界面(如默认的 http://localhost:8501),此时按侧边栏顺序输入 BROWSERBASE_API_KEY、填写表单、点击搜索,等待后台 Agent 完成"搜索 → 加载详情与比价 → 汇总"的完整流程,即可看到最终推荐结果。app_openai.py 以同样方式运行,只是需要额外的 OPENAI_API_KEY 环境变量。

从源码看到的注意事项与扩展方向

结合对源码的逐文件核对,有几点值得读者在实际运行时注意:

  • 图片资源:app.py 与 app_openai.py 的侧边栏都引用了 ./assets/browser-base.png 用于展示 Browserbase 图标,但当前仓库快照的 hotel-booking-crew 目录下并未包含 assets/ 目录。若在你的环境中该文件同样缺失,可能影响侧边栏渲染,可选择自行补充图片或删除对应 st.image(...) 调用(位于两个 app 文件第 29 行附近)。
  • 固定等待时长:browserbase 工具内的 sleep(25) 是整体硬编码的,网络波动或目标站点改版都可能改变实际需要的等待时间;页面结构变化后 html2text 提取的内容质量也需要重新验证。
  • 地点编码:Kayak URL 需要特定地点编码,LLM 是否总能正确推断 -p15321 这类后缀存在不确定性,必要时可在工具或提示词中提供地点字典作为兜底。
  • 同一套代码的复用价值:由于 Agent 的工具被拆分为独立模块(browserbase.py、kayak.py),这套"URL 构造工具 + 浏览器抓取工具 + CrewAI 多角色编排"的模式可以低成本迁移到机票、租车、餐厅预订等其他垂直场景——仓库中同类的 flight-booking-crew 项目即是这种复用的直接体现。

综上,hotel-booking-crew 是一个小而完整的工程范本:它以 CrewAI 定义"检索 + 汇总"两个职责分明的 Agent,用 @tool 把 Browserbase 无头浏览器能力暴露给 LLM,再通过 Streamlit 把多智能体执行封装成普通用户可操作的搜索表单,并用统一的 LLM() 入口保留了切换本地 DeepSeek-R1 与云端模型的灵活性。对照 README 的指引、沿着 app.py → kayak.py/browserbase.py 的源码逐层阅读,即可完整掌握从"用户输入"到"真实网页检索"再到"结构化汇总输出"的整条多智能体落地链路。

【免费下载链接】ai-engineering-hub In-depth tutorials on LLMs, RAGs and real-world AI agent applications. 【免费下载链接】ai-engineering-hub 项目地址: https://gitcode.com/GitHub_Trending/ai/ai-engineering-hub

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐