使用 ADK 从原型设计过渡到代理

1. 概览

如今,使用 AI 构建应用从何处着手?对于我们大多数人来说,这通常始于一个简单的问题:“模型是否真的能帮助解决我一直在思考的问题?”这正是 Google AI Studio 的用武之地。您可以在这里快速设计任何原型。我一直想改造厨房,我猜 Gemini 能帮上忙,但我是一名工程师,而不是总承包商。我甚至不确定该问什么问题,因为需要考虑的因素太多了:法规、固定装置等。因此,让我们先将问题分解,让 Gemini 为我们生成一个非常详细的提示,然后生成完整的装修计划,并直观呈现改造效果!但请稍等。从这里开始,我该如何真正帮助企业实现规模化发展?输入 AGENTS!!!

智能体是一种自主程序,它会与 AI 模型对话,使用其拥有的工具和上下文执行基于目标的操作,并且能够根据事实自主做出决策!

智能体开发套件 (ADK)

智能体开发套件 (ADK) 是一个灵活的模块化框架,用于开发和部署 AI 智能体。ADK 支持通过将多个不同的代理实例组合成多代理系统 (MAS) 来构建复杂的应用。

在 ADK 中,多智能体系统是指一个应用,其中不同的智能体(通常形成层次结构)通过协作或协调来实现更大的目标。以这种方式构建应用具有显著优势,包括增强的模块化、专业化、可重用性、可维护性,以及使用专用工作流代理定义结构化控制流的能力。

构建内容

准备好从我们的原型 PROMPT 转到构建智能体了吗?我们将创建一个代理,以帮助生成厨房改造项目的提案文档。在本实验中,您将:

  1. 使用 ADK 构建一个简单的智能体来生成装修提案文档
  2. 将生成的装修提案文档存储在 Cloud Storage 存储分区中
  3. 在 Cloud Shell 和代理网页输出中测试代理

要求

  • 一个浏览器,例如 ChromeFirefox
  • 启用了结算功能的 Google Cloud 项目。

2. 准备工作

创建项目

  1. Google Cloud Console 的项目选择器页面上,选择或创建一个 Google Cloud 项目
  2. 确保您的 Cloud 项目已启用结算功能。了解如何检查项目是否已启用结算功能
  3. 此外,如果您正在阅读本文,并希望获得一些赠金来帮助您开始使用 Google Cloud 和 ADK,请使用此链接 兑换赠金
  4. 您可以按照此处说明兑换。请注意,此链接仅在 2025 年 7 月 15 日之前有效,可用于兑换。
  5. 点击此链接,激活 Cloud Shell。您可以点击 Cloud Shell 中的相应按钮,在 Cloud Shell 终端(用于运行云命令)和编辑器(用于构建项目)之间切换。
  6. 连接到 Cloud Shell 后,您可以使用以下命令检查自己是否已通过身份验证,以及项目是否已设置为您的项目 ID:
gcloud auth list
  1. 在 Cloud Shell 中运行以下命令,以确认 gcloud 命令了解您的项目。
gcloud config list project
  1. 如果项目未设置,请使用以下命令进行设置:
gcloud config set project <YOUR_PROJECT_ID>
  1. 确保已安装 Python 3.9 及更高版本

如需了解其他 gcloud 命令和用法,请参阅文档

3. 原型

前往 Google AI Studio。开始输入提示。我的提示如下:

I want to renovate my kitchen, basically just remodel it. I don't know where to start. So I want to use Gemini to generate a plan. For that I need a good prompt. Give me a short yet detailed prompt that I can use.

调整和配置右侧的参数,以获得最佳回答。

根据这个简单的描述,Gemini 为我生成了一个非常详细的提示,让我可以开始装修了!实际上,我们是使用 Gemini 来让 AI Studio 和我们的模型提供更出色的回答。您还可以根据自己的应用场景选择要使用的不同模型。

我们选择了 Gemini 2.5 Pro。这是一个思考模型,这意味着我们可以获得更多输出令牌,在本例中最多可获得 65K 个令牌,用于生成长篇分析和详细文档。启用 Gemini 2.5 Pro 后,系统会显示 Gemini 思考框,该模型具有原生推理能力,可以处理长上下文请求。

请参阅下方的响应代码段:

4e4361663df80964.png

AI Studio 分析了我的数据,并生成了橱柜、台面、防溅挡板、地板、水槽、内聚性、调色板和材料选择等所有这些内容。Gemini 甚至还会注明信息来源!

现在,尝试使用不同的提示,看看创意如何变为现实。

  1. 复制此提示并将其粘贴到提示编辑器中:
Add flat and circular light accessories above the island area for my current kitchen in the attached image.
  1. 附上您当前厨房的照片(您也可以使用我的厨房示例图片)。
  2. 将模型更改为“Gemini 2.0 Flash 预览版图片生成”,以便您能够生成图片。

我获得了以下输出

fb33e7b1f6560a0c.png

这就是 Gemini 的强大之处!

从理解视频、原生图片生成到借助 Google 搜索建立真实信息依据,有些功能只能通过 Gemini 构建。

在 AI Studio 中,您可以获取此原型,然后获取 API 密钥,并使用 Vertex AI ADK 的强大功能将其扩展为完整的代理应用。

4. ADK 设置

现在,我们前往在“准备工作”部分中激活的 Cloud Shell 终端:

  1. 创建并激活虚拟环境(推荐)

在 Cloud Shell 终端中,创建虚拟环境:

python -m venv .venv

激活虚拟环境:

source .venv/bin/activate
  1. 安装 ADK
pip install google-adk

5. 项目结构

  1. 在 Cloud Shell 终端中,在所需项目位置为您的 agentic 应用创建一个根目录:
mkdir agentic-apps
  1. 在主目录中,创建一个特定于当前项目的文件夹:
mkdir renovation-agent
  1. 前往 Cloud Shell 编辑器,然后创建以下项目结构(先创建空文件):
renovation-agent/
        __init__.py
        agent.py
        requirements.txt
        .env

6. 源代码

  1. 前往“init.py”,并更新为以下内容:
from . import agent
  1. 前往 agent.py,然后使用以下路径中的以下内容更新该文件:

在 agent.py 中,我们导入必要的依赖项,从 .env 文件中检索配置参数,并定义 root_agent,该代理会生成提案文档并将其存储在 Cloud Storage 存储分区中。为了完成 Cloud Storage 步骤,我们将使用名为 store_pdf 的工具。

注意:目前,PDF 尚未设置格式!根据社区开发者的 PR,以下代码段已包含在此处 [未经测试],您可以随意将其纳入 store_pdf 方法中

doc = SimpleDocTemplate(
        pdf_buffer,
        pagesize=letter,
        rightMargin=0.75 * inch,
        leftMargin=0.75 * inch,
        topMargin=0.75 * inch,
        bottomMargin=0.75 * inch
    )

    styles = getSampleStyleSheet()
    story = []

    # --- CUSTOM STYLES FOR HEADERS ---
    # Define a new style for section headers
    styles.add(ParagraphStyle(name='SectionHeader',
                              parent=styles['Normal'],
                              fontName='Helvetica-Bold', # Make it bolder
                              fontSize=14,               # Make it slightly larger
                              leading=16,                # Line spacing
                              spaceAfter=0.15 * inch,    # Space after the header
                              spaceBefore=0.25 * inch,   # Space before the header
                              textColor=black            # Ensure color is bright/black (default is usually black, but explicit is good)
                             ))

    # Define a style for the main document title
    styles.add(ParagraphStyle(name='DocumentTitle',
                              parent=styles['Normal'],
                              fontName='Helvetica-Bold',
                              fontSize=20,
                              leading=24,
                              spaceAfter=0.25 * inch,
                              alignment=TA_CENTER, # Center align the title
                              textColor=black
                             ))
    # ---------------------------------

    paragraphs_raw = pdf_text.split('\n\n')

    # Heuristic for the garbled line issue (as before, temporary)
    if paragraphs_raw and len(paragraphs_raw[-1]) < 50 and any(char in paragraphs_raw[-1] for char in ['io', 'og', 'al', 'op']):
         logger.warning("Detected potentially garbled last paragraph. Attempting to trim/omit.")
         paragraphs_raw[-1] = "11. Entire Agreement:\nThis proposal constitutes the entire agreement between the parties and supersedes all prior discussions and agreements."


    for i, para_text in enumerate(paragraphs_raw):
        para_text = para_text.strip()
        if not para_text:
            continue

        # Special handling for the main document title (PROPOSAL DOCUMENT)
        if i == 0 and "PROPOSAL DOCUMENT" in para_text.upper():
            p = Paragraph("PROPOSAL DOCUMENT", styles['DocumentTitle'])
            story.append(p)
            story.append(Spacer(1, 0.15 * inch)) # Add space after the title
            # Skip the rest of this initial block if it's just the title
            remaining_text_lines = para_text.splitlines()[1:]
            if remaining_text_lines:
                formatted_text = "<br/>".join(remaining_text_lines)
                p = Paragraph(formatted_text, styles['Normal'])
                story.append(p)
                story.append(Spacer(1, 0.1 * inch))
            continue # Move to the next paragraph

        # Check if the paragraph looks like a section header (e.g., starts with a number and dot or just bold text)
        # This is a heuristic and might need fine-tuning based on actual proposal content variability.
        is_section_header = False
        # Check for numbered sections (e.g., "1. Scope of Work:")
        if para_text.startswith(('1.', '2.', '3.', '4.', '5.', '6.', '7.', '8.', '9.', '10.', '11.')):
            is_section_header = True
        # Check for Exhibit headers (e.g., "Exhibit A: Cabinet Design") or Roman numeral headings
        elif para_text.startswith(('Exhibit ', 'I.', 'II.', 'III.', 'IV.', 'V.', 'VI.', 'VII.')):
            is_section_header = True
        # Check for specific known headers
        elif para_text.strip().upper() in ["IN WITNESS WHEREOF,", "EXHIBITS:"]:
            is_section_header = True


        if is_section_header:
            p = Paragraph(para_text, styles['SectionHeader'])
            story.append(p)
            # No additional Spacer here, as SectionHeader style has spaceAfter
        else:
            formatted_text = para_text.replace('\n', '<br/>')
            p = Paragraph(formatted_text, styles['Normal'])
            story.append(p)
            story.append(Spacer(1, 0.1 * inch)) # Standard space after body paragraphs

    doc.build(story)

    pdf_buffer.seek(0)

    # Upload the PDF to GCS
    storage_client = storage.Client()
    bucket = storage_client.bucket(STORAGE_BUCKET)
    blob = bucket.blob(PROPOSAL_DOCUMENT_FILE_NAME)

    blob.upload_from_file(pdf_buffer, content_type="application/pdf")

    logger.info(f"Successfully uploaded PDF to gs://{STORAGE_BUCKET}/{PROPOSAL_DOCUMENT_FILE_NAME}")

except Exception as e:
    logger.error(f"Error writing text to PDF and uploading: {e}")
    raise
finally:
    if 'pdf_buffer' in locals():
        pdf_buffer.close()
return "Successfully uploaded PDF to GCS!!"
  1. 确保您拥有 Cloud Storage 存储分区

用于存储代理生成的提案文档。创建该服务账号并配置访问权限,以便我们使用 Vertex AI 创建的智能体系统可以访问该服务账号。具体方法如下:

https://cloud.google.com/storage/docs/creating-buckets#console

将存储分区命名为“next-demo-store”。如果您将其命名为其他名称,请务必更新 .env 文件(在“设置环境变量”步骤中)中 STORAGE_BUCKET 的值。

  1. 如需设置对存储分区的访问权限,请前往 Cloud Storage 控制台,然后前往您的存储分区(在本例中,存储分区名称为“next-demo-storage”:https://console.cloud.google.com/storage/browser/next-demo-storage)。

依次前往“权限”>“查看主账号”>“授予访问权限”。选择“allUsers”作为正文,并选择“Storage Object User”作为角色。

Make sure to not enable "prevent public access". Since this is a demo/study application we are going with a public bucket. Remember to configure permission settings appropriately when you are building your application.
  1. 创建依赖项列表

requirements.txt 中列出所有依赖项。您可以从 repo 中复制此内容。

单代理系统源代码说明

agent.py 文件使用智能体开发套件 (ADK) 定义了厨房改造多智能体系统的结构和行为。下面我们来分解一下关键组件:

代理定义

根代理(编排器):proposal_agent

root_agent 充当此单代理系统的编排器。它会接收初始装修请求,并根据请求的需求确定要调用哪些工具。

然后,根代理会收集工具的回答,并将它们合并起来,为用户提供全面的回答。在本例中,我们只有一个工具“store_pdf”。

7. 数据流和关键概念

用户通过 ADK 界面(终端或网页界面)发起请求。

  1. 根代理收到请求。
  2. 根代理会分析请求,并在需要时将其路由到工具。
  3. “store_pdf”工具旨在将翻新的文本内容写入 PDF 文件,然后将其上传到 Google Cloud Storage。
  4. 然后,该代理会将响应返回给根代理。
  5. 根代理会合并响应,并向用户提供最终输出。

LLM(大语言模型)

智能体主要依靠 LLM 来生成文本、回答问题和执行推理任务。LLM 是智能体能够理解和响应用户请求的“大脑”。我们在此应用中使用的是 Gemini 2.5。

Google Cloud Storage

用于存储生成的装修方案文档。您需要创建一个存储分区,并授予代理访问该存储分区所需的权限。

Cloud Run(可选)

OrderingAgent 使用 Cloud Run functions 与 AlloyDB 进行交互。Cloud Run 提供了一个无服务器环境,用于执行代码以响应 HTTP 请求。

AlloyDB

如果您使用的是 OrderingAgent,则需要设置 AlloyDB 数据库来存储订单信息。

.env 文件

.env 文件用于存储 API 密钥、数据库凭据和存储分区名称等敏感信息。请务必确保此文件的安全性,并且不要将其提交到代码库中。它还存储代理和 Google Cloud 项目的配置设置。root_agent 或支持函数通常会从此文件中读取值。确保在 .env 文件中正确设置了所有必需的变量。这包括 Cloud Storage 存储分区名称

8. 模型设置

代理理解用户请求和生成回答的能力由大语言模型 (LLM) 提供支持。您的代理需要安全地调用此外部 LLM 服务,这需要身份验证凭据。如果没有有效的身份验证,LLM 服务会拒绝代理的请求,并且代理将无法正常运行。

  1. Google AI Studio 获取 API 密钥。
  2. 在下一步中,您将设置 .env 文件,并将 <<your API KEY>> 替换为您的实际 API 密钥值。

9. 环境变量设置

  1. 在此代码库中,为模板 .env 文件中的参数设置值。在我的示例中,.env 包含以下变量:
GOOGLE_GENAI_USE_VERTEXAI=FALSE
GOOGLE_API_KEY=<<your API KEY>>
GOOGLE_CLOUD_LOCATION = us-central1 <<or your region>>
GOOGLE_CLOUD_PROJECT = <<your project id>>
PROJECT_ID = <<your project id>>
GOOGLE_CLOUD_REGION=us-central1 <<or your region>>
STORAGE_BUCKET = next-demo-store <<or your storage bucket name>>

将占位符替换为您的值。

10. 运行代理

  1. 使用终端,前往智能体项目的父级目录:
cd agentic-apps/renovation-agent
  1. 安装所有依赖项
pip install -r requirements.txt
  1. 您可以在 Cloud Shell 终端中运行以下命令来执行代理:
adk run .
  1. 您可以运行以下命令,在 ADK 配置的 Web 界面中运行该应用:

注意:您必须从智能体项目文件夹外部运行此命令,退出该文件夹,然后再运行此命令

adk web
  1. 使用以下提示进行测试:
user>> 

Hello. Generate Proposal Document for the kitchen remodel requirement in a proper format that applies to a renovation contract. Remember this text will eventually be stored as a pdf file so make sure to have the formatting appropriate. I have no other specification.

11. 结果

对于命令 adk run .result is as follows"

2703603a907329ae.png

ae56b38cc6da9afe.png

91452a4de933a75b.png

您可以验证装修提案文档是否已在 Cloud Storage 存储分区中创建。

12. 部署到 Cloud Run

  1. 在项目根文件夹中创建一个名为 Dockerfile 的文件:
cd agentic-apps/renovation-agent
  1. 从 GitHub 代码库复制内容
https://github.com/AbiramiSukumaran/adk-renovation-single-agent/blob/main/Dockerfile

到此 Dockerfile 文件中。

  1. 使用以下命令在 Cloud Run 上进行部署:
adk deploy cloud_run --project=abis-345004 --region=us-central1 --service_name=renovation-agent --app_name=renovation-app --with_ui .

大功告成。部署完成后,您应该会在终端中看到端点,该端点即可供您使用。

13. 清理

为避免系统因本博文中使用的资源向您的 Google Cloud 账号收取费用,请按照以下步骤操作:

  1. 在 Google Cloud 控制台中,前往管理资源页面。
  2. 在项目列表中,选择要删除的项目,然后点击删除
  3. 在对话框中输入项目 ID,然后点击关停以删除项目。

14. 恭喜

恭喜!您已成功使用 ADK 创建并与多智能体应用互动!多代理系统旨在通过自动执行提案生成、许可检查和订单状态跟踪等任务来简化厨房改造流程。每个代理都有特定的角色,而根代理会协调它们的活动,以提供全面的解决方案。该系统利用 LLM、Google Cloud 服务以及可能的外部 API 来实现其功能。点击此处可查看产品文档。