Xây dựng Trình tạo bản đề xuất dựa trên tác nhân: Điều phối, đánh giá và phân tích bằng Google Cloud

1. Giới thiệu

biểu ngữ lớp học chuyên sâu dành cho nhân viên

Tìm hiểu cách xây dựng một công cụ đa tác nhân đa phương thức, có thể lấy một bản tóm tắt thô về chủ đề và điều phối việc tạo ra những thành phần hấp dẫn trên mạng xã hội. Bằng cách sử dụng Bộ công cụ phát triển tác nhân (ADK) và giao thức Agent2Agent (A2A) của Google, bạn sẽ tạo các tác nhân vi mô tự động, đóng gói các quy tắc về thương hiệu dưới dạng một kỹ năng tác nhân có thể dùng lại, triển khai dịch vụ tác nhân cho Cloud Run và chấm điểm các hình ảnh chính được tạo để đảm bảo tuân thủ thương hiệu bằng các hàm AI của BigQuery.

Bạn sẽ thực hiện

Trong lớp học lập trình này, bạn sẽ tìm hiểu cách thực hiện các thao tác sau:

  • Điều phối nhiều tác nhân ADK bằng quy trình công việc đồ thị xác định.
  • Đóng gói nguyên tắc sử dụng thương hiệu dưới dạng một kỹ năng của tác nhân chỉ tải khi cần.
  • Trang bị cho một tác nhân công cụ tạo hình ảnh giúp lưu kết quả đầu ra dưới dạng một cấu phần phần mềm.
  • Hiển thị một tác nhân dưới dạng dịch vụ A2A và gọi tác nhân đó từ một tác nhân khác dưới dạng một nút từ xa.
  • Triển khai Visual Director lên Cloud Run và gọi quy trình công việc qua A2A.
  • Chấm điểm hình ảnh chính được tạo để phù hợp với thương hiệu trong BigQuery bằng các hàm AI.

Bạn cần có

  • Một trình duyệt web (chẳng hạn như Chrome).
  • Một tài khoản Google Cloud và một Dự án trên Google Cloud đã bật tính năng thanh toán.

2. Trước khi bắt đầu

Tạo một dự án trên Google Cloud

  1. Trong Google Cloud Console, trên trang chọn dự án, hãy chọn hoặc tạo một dự án trên Google Cloud.
  2. Đảm bảo bạn đã bật tính năng thanh toán cho dự án trên Cloud. Tìm hiểu cách kiểm tra xem tính năng thanh toán có được bật trên một dự án hay không.

Khởi động Cloud Shell

Bạn sẽ sử dụng Google Cloud Shell để tải mã xuống, chạy tập lệnh thiết lập và triển khai ứng dụng.

  1. Mở Google Cloud Shell trong trình duyệt (hoặc nhấp vào Kích hoạt Cloud Shell ở đầu Google Cloud Console):
    https://shell.cloud.google.com/
    
    Nhấp vào Tiếp tục nếu bạn nhận được thông báo bổ sung.Đợi cho đến khi cả thiết bị đầu cuối và trình chỉnh sửa được tải đầy đủ. Quá trình này có thể mất một hoặc hai phút.
  2. Định cấu hình mã dự án và khu vực trên Google Cloud (ví dụ: us-central1) trong môi trường của bạn bằng cách dán mã này vào thiết bị đầu cuối (thay thế và bằng các giá trị của bạn):
    export PROJECT_ID="<YOUR_PROJECT_ID>"
    export REGION="<YOUR_REGION>"
    gcloud config set project $PROJECT_ID
    
    Dán vào ứng dụng TerminalBạn sẽ thấy một thông báo tương tự như sau:
    Your active configuration is: [cloudshell-####]
    Updated property [core/project]
    
  3. Bật các Cloud APIs của Google Cloud bắt buộc cho dự án của bạn:
    gcloud services enable \
      compute.googleapis.com \
      cloudresourcemanager.googleapis.com \
      aiplatform.googleapis.com \
      storage.googleapis.com \
      storage-component.googleapis.com \
      bigqueryconnection.googleapis.com \
      bigquery.googleapis.com \
      run.googleapis.com \
      secretmanager.googleapis.com \
      cloudbuild.googleapis.com \
      artifactregistry.googleapis.com \
      iap.googleapis.com \
      iam.googleapis.com
    

Sao chép kho lưu trữ

Chúng tôi có một mẫu mã để giúp bạn bắt đầu với lớp học lập trình này. Mặc dù tương đối nhỏ, nhưng kho lưu trữ GitHub nơi lưu trữ lại khá lớn. Để tăng tốc quá trình sao chép và tiết kiệm dung lượng ổ đĩa, chúng ta thường dùng git clone với tuỳ chọn --filter=blob:none, git sparse-checkout và một số lệnh khác.

Nghe có vẻ rất phức tạp, nhưng Antigravity có thể làm tất cả cho chúng ta!

Trước tiên, hãy định cấu hình Antigravity CLI để có thể giải quyết ngay vấn đề của chúng ta và khởi chạy nó.

Trong Cloud Shell, hãy chạy lệnh sau:

source <(curl -fsSL https://github.com/GoogleCloudPlatform/devrel-demos/raw/refs/heads/vladkol/cloud-shell-configure-agy/codelabs/labs-scripts/bwg-multi-agent-pitch-generator/cloud_shell_configure_agy.sh)

Lệnh này sẽ định cấu hình Antigravity CLI và khởi chạy lệnh đó trong cùng một thiết bị đầu cuối. Khi thoát Antigravity CLI, bạn sẽ quay lại dấu nhắc Cloud Shell.

Antigravity CLI

Chúng ta chỉ cần thư mục codelabs/bwg-multi-agent-pitch-generator trong kho lưu trữ. Hãy yêu cầu Antigravity tải xuống thư mục đó và lưu trữ trên thiết bị trong ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/.

Giao cho Antigravity CLI nhiệm vụ sau:

Download files from under `https://github.com/GoogleCloudPlatform/devrel-demos/tree/main/codelabs/bwg-multi-agent-pitch-generator`
to folder `~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/`.
If `devrel-demos` folder already exists, rename it with a `.backup` suffix.

Câu lệnh Antigravity CLI

Antigravity sẽ thực hiện tác vụ này. Công cụ này sẽ yêu cầu bạn cấp quyền để chạy một số lệnh. Sau khi tải mã xuống, hãy thoát Antigravity CLI bằng cách thực thi lệnh /exit:

/exit

Thoát khỏi Antigravity CLI

Mở không gian làm việc của kho lưu trữ

Bây giờ, hãy mở không gian làm việc mã trong Cloud Shell. Chạy lệnh này trong cửa sổ dòng lệnh Cloud Shell:

cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/
cloudshell workspace .

Chạy tập lệnh thiết lập

Để có trải nghiệm mượt mà hơn, hãy chạy tập lệnh thiết lập từ kho lưu trữ. Thao tác này định cấu hình các biến môi trường, cài đặt agents-cli, tạo bộ chứa Cloud Storage cho các cấu phần phần mềm, tạo kết nối Tài nguyên đám mây BigQuery và cấp các quyền IAM cần thiết cho tài khoản dịch vụ của bộ chứa này bằng tính năng tự động thử lại.

Tập lệnh thiết lập cũng gọi setenv.sh, một trình trợ giúp nhỏ giúp lấy các biến mà cả hai tác nhân đều cần (GOOGLE_CLOUD_PROJECT, GOOGLE_CLOUD_REGION, GOOGLE_CLOUD_LOCATION và GOOGLE_GENAI_USE_VERTEXAI) từ PROJECT_ID và REGION mà bạn đã xuất ở trên. Thao tác này cũng lưu 2 giá trị đó vào tệp .env trong thư mục gốc để bạn có thể khôi phục sau này.

  1. Trong Cloud Shell, hãy thực thi tập lệnh thiết lập và kích hoạt tập lệnh đó:
    chmod +x codelab-setup.sh setenv.sh
    ./codelab-setup.sh
    
  2. Quá trình này sẽ mất khoảng một phút để chạy. Bạn sẽ thấy thông báo này cho biết đã thực hiện thành công: "🎉 🦄 Script execution complete."thành công (Đã hoàn tất việc thực thi tập lệnh).
  3. Tập lệnh thiết lập chạy trong một shell riêng, vì vậy các biến của tập lệnh này không đến được thiết bị đầu cuối của bạn. Tải các tệp đó vào phiên của bạn:
    source ./setenv.sh
    
    Bạn sẽ thấy dự án, vùng và vị trí của mình được phản hồi.

3. Điều phối các tác nhân bằng quy trình công việc dạng biểu đồ

Trong nhiệm vụ này, bạn sẽ tạo dự án trình tạo bản đề cử và xây dựng một quy trình công việc dạng biểu đồ chạy hai tác nhân và kết hợp đầu ra của chúng.

Một bản đề cử cần có một khái niệm, tức là ý tưởng và các thành phần bán khái niệm đó. Trong trường hợp sử dụng này, chúng ta sẽ thiết kế các tác nhân có trách nhiệm khác nhau trong phạm vi công việc, minh hoạ cách bạn có thể tách các tác vụ phức tạp và uỷ quyền cho các tác nhân khác nhau có trách nhiệm riêng biệt, giúp bạn dễ dàng quản lý các tác vụ đó.

Việc phân chia loại công việc này giữa các chuyên gia đòi hỏi phải có một cơ chế phối hợp khi mỗi tác nhân chạy. Bạn có thể chọn để tác nhân của mình quyết định thời điểm gọi từng tác nhân phụ hoặc áp dụng một phương pháp mang tính xác định hơn. Trong lớp học lập trình này, bạn sẽ sử dụng một phương pháp xác định với quy trình làm việc đồ thị.

Tạo giàn giáo cho dự án tác nhân đầu tiên

  1. Trong Cloud Shell, hãy tạo dự án bằng cách dán lệnh này vào thiết bị đầu cuối:
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator
    agents-cli create pitch-generator \
      --agent adk \
      --agent-directory pitch_generator \
      --deployment-target cloud_run \
      --session-type in_memory \
      --cicd-runner skip \
      --region <YOUR_REGION> \
      --auto-approve
    
    Lệnh này sẽ ghi một dự án hoàn chỉnh, có thể chạy: một tác nhân, một Dockerfile, các thử nghiệm, Terraform cho sau này và một tệp .env có mẫu .env.example. Có 2 phần quan trọng trong lớp học lập trình này:
    • pitch_generator/agent.py là tác nhân và tệp mà bạn viết lại.
    • pitch_generator/ là tên gói, do --agent-directory đặt.
    Tác nhân đọc dự án và khu vực của dự án từ môi trường mà bạn đã xuất bằng setenv.sh, vì vậy, bạn không cần định cấu hình gì ở đây.
  2. Cài đặt các phần phụ thuộc:
    cd pitch-generator
    agents-cli update -y && agents-cli install && uv sync --upgrade-package google-adk
    
    Lưu ý: Khung này cũng kết nối dự án để phân phát A2A, giao thức được dùng sau này trong lớp học lập trình này. Tạm thời không động đến những tệp đó.

Tìm hiểu biểu đồ

ADK phiên bản 2.x hỗ trợ quy trình làm việc bằng biểu đồ. Biểu đồ được tạo từ nút (đơn vị công việc) và cạnh (kết nối các nút). Nút thường là một tác nhân, mặc dù một hàm Python đơn giản cũng hoạt động. Một cạnh cho biết khi một nút kết thúc, nút khác sẽ chạy tiếp theo.

Các cạnh được viết dưới dạng danh sách và ADK sẽ xác định thứ tự:

edges=[
    ("START", creative_director),        ## the graph begins here
    (creative_director, copywriter),     ## then this
]

Việc lồng ghép này mang lại ý nghĩa cho một cạnh:

Edge

Ý nghĩa

(a, b, c)

Một chuỗi. a, rồi đến b, rồi đến c, lần lượt

(a, (b, c))

Một phân đầu ra. a, sau đó là b và c cùng một lúc

((a, b), c)

Một fan-in. c chờ cả a và b

Đối với một fan-in, bạn sử dụng JoinNode, chờ mọi tiền nhiệm và truyền các đầu ra của chúng cùng nhau.

Trong nhiệm vụ này, chúng ta sẽ tạo quy trình sau:

biểu đồ quy trình công việc ban đầu

Xây dựng quy trình

  1. Mở tệp tác nhân bằng cách dán nội dung này vào dòng lệnh:
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator
    cloudshell edit pitch_generator/agent.py
    
    Bạn sẽ thấy agent.py được giàn giáo trong trình chỉnh sửa Cloud Shell.💡 Lưu ý: Bất cứ lúc nào, bạn cũng có thể chọn mã và dùng biểu tượng bóng đèn để yêu cầu Gemini giải thích mã đó. Bạn cũng có thể hỏi Antigravity CLI.Sử dụng Gemini Code Assist
  2. Thay thế toàn bộ tệp trong pitch_generator/agent.py trong trình chỉnh sửa bằng nội dung sau:
    import mimetypes
    import os
    
    from dotenv import load_dotenv
    from google.adk.agents import Agent
    from google.adk.agents.context import Context
    from google.adk.agents.remote_a2a_agent import (
       AGENT_CARD_WELL_KNOWN_PATH,
       RemoteA2aAgent,
    )
    from google.adk.apps import App
    from google.adk.events.event import Event
    from google.adk.models import Gemini
    from google.adk.workflow import JoinNode, Workflow
    from google.genai import types
    
    load_dotenv()
    
    MODEL = "gemini-3.8-flash"
    
    def _model() -> Gemini:
       return Gemini(
          model=MODEL,
          client_kwargs={"location": "global"},
          retry_options=types.HttpRetryOptions(attempts=3),
       )
    
    """ ⬇️ Configure visual-director RemoteA2aAgent agent below this comment """
    
    
    creative_director = Agent(
       name="creative_director",
       model=_model(),
       description="Turns a raw idea into a campaign concept.",
       instruction=(
          "You are the Creative Director. Turn the idea you are given into ONE "
          "punchy campaign concept line with the rationale explanation behind it."
       ),
       output_key="creative_director",
    )
    
    copywriter = Agent(
       name="copywriter",
       model=Gemini(model=MODEL, retry_options=types.HttpRetryOptions(attempts=3)),
       description="Writes social copy for a campaign concept.",
       instruction=(
          "You are the Copywriter. Write ONE short social caption for the "
          "campaign concept you are given. Under 25 words."
       ),
    )
    
    """ Waits for every node wired into it, then hands their outputs on together."""
    assemble = JoinNode(name="assemble")
    
    def package(node_input: dict):
       """Render the joined branches as the finished pitch.
       """
    
       pitch = "\n\n".join(
          [
                f"CONCEPT\n{node_input['creative_director']}",
                f"COPY\n{node_input['copywriter']}",
          ]
       )
       """ The first event is what the user sees. The second is the node's output,
       which any downstream node would receive."""
       yield Event(content=types.Content(role="model", parts=[types.Part(text=pitch)]))
       yield Event(output=pitch)
    
    """ we put creative_director before `assemble`  so that we can
       gather the `Concept` output at assemble"""
    root_agent = Workflow(
       name="pitch_generator",
       edges=[
          ("START", creative_director),
          (creative_director, copywriter),
          ((creative_director, copywriter), assemble),
          (assemble, package),
       ],
    )
    
    app = App(
       root_agent=root_agent,
       name="pitch_generator",
    )
    
  3. agents-cli bao gồm một giao diện người dùng phát triển web tích hợp có tên là playground để kiểm tra, tương tự như ADK. Cách bắt đầu sử dụng Playground:
    agents-cli playground --port 8080
    
    Bạn sẽ được hỏi xem bạn có muốn bật tính năng đo từ xa hay không. Nhập n rồi nhấn Enter.
  4. Nhấp vào Web Preview (Xem trước trên web), biểu tượng con mắt ở trên cùng bên phải của thiết bị đầu cuối, rồi chọn Preview on port 8080 (Xem trước trên cổng 8080).nhấp vào bản xem trước trên web
  5. Bạn sẽ thấy giao diện sau đây để trò chuyện với nhân viên hỗ trợ. Hãy thử gửi câu lệnh "Ván trượt bay cho mèo". Bạn sẽ thấy một câu trả lời như bên dưới, trong đó bạn cũng có thể kiểm tra toàn bộ biểu đồ quy trình làm việc.quy trình minh hoạKiểm tra cách câu trả lời đó được tạo ra. Sân chơi cho thấy từng bước mà biểu đồ đã thực hiện theo thứ tự: creative_director, sau đó là copywriter, rồi assemble, sau đó là package. Trình tự đó là biểu đồ mà bạn đã kết nối, nhờ đó, bạn có thể nhanh chóng xác nhận biểu đồ mà bạn vừa tạo.
  6. Quay lại thiết bị đầu cuối và dừng quy trình agents-cli playground đang chạy bằng cách nhấn tổ hợp phím Ctrl+C hai lần.

4. Giao một kỹ năng cho tác nhân

Trong nhiệm vụ này, bạn sẽ tạo một tác nhân thứ hai và cung cấp cho tác nhân đó một bộ nguyên tắc sử dụng thương hiệu được đóng gói dưới dạng một kỹ năng.

Quy trình này ghi các từ. Một bức ảnh cần có phong cách riêng: bảng màu, cách chiếu sáng, bố cục bạn sử dụng và bố cục bạn không bao giờ sử dụng. Đó là một trang quy tắc và trang quy tắc cần có một cơ chế quản lý hiệu quả hơn. Một cách để thực hiện việc này là đóng gói nó dưới dạng "kỹ năng của tác nhân".

Kỹ năng là một thư mục chứa tệp SKILL.md. Cấu trúc của thư mục kỹ năng có dạng như ví dụ sau (sử dụng visual_director, tác nhân tiếp theo mà bạn sẽ tạo):

visual_director/
   ...
   agent.py
   skills/
      brand-guidelines/
         SKILL.md          ## required: frontmatter + instructions
         references/       ## optional: longer supporting documents
         assets/           ## optional: templates, images, data
         scripts/          ## optional: executable helpers
   ...

Điểm truy cập của kỹ năng tác nhân là tệp SKILL.md, bao gồm một phần đầu trang YAML như phần được minh hoạ dưới đây:

---
name: brand-guidelines
description: The house brand rules for a key visual. Load this before writing art direction, so the visual looks like it came from this brand.
---

## Brand guidelines

Deep indigo and slate as the ground. One warm accent, amber or terracotta...

frontmatter là nhãn. Đó là tất cả những gì mà tác nhân thấy ngay từ đầu, đó là lý do nội dung mô tả phải cho biết thời điểm kỹ năng đáng được tải. body là nội dung và nội dung này sẽ nằm ngoài ngữ cảnh cho đến khi tác nhân gọi load_skill.

Đó chính là điểm khác biệt. Câu lệnh vẫn ngắn gọn, tác nhân chỉ lấy các quy tắc khi cần và việc thay đổi kiểu có nghĩa là chỉnh sửa một tệp thay vì tìm kiếm trong một chuỗi hướng dẫn.

Chúng ta sẽ bắt đầu xây dựng tác nhân thứ hai, visual-director, sử dụng các kỹ năng của tác nhân như minh hoạ trong sơ đồ bên dưới:

trang bị kỹ năng cho tác nhân thứ hai

Tạo giàn giáo cho Trình chỉ đạo hình ảnh

Bạn sẽ tạo một tác nhân chuyên biệt để xử lý thành phần trực quan, visual-director. Đây là một dự án riêng biệt vì sau này dự án này sẽ trở thành dịch vụ riêng để minh hoạ cách nhiều tác nhân giao tiếp với nhau bằng giao thức A2A. Bạn sẽ có 2 dự án song song:

bwg-multi-agent-pitch-generator/
├── pitch-generator/              ## built in the last task
│   ├── pitch_generator/
│   │   ├── agent.py              ## the graph you wrote
│   │   ├── app_utils/            ## generated, A2A wiring
│   │   └── fast_api_app.py       ## generated, serves the agent
│   └── pyproject.toml
└── visual-director/              ## created below
    └── ...                       ## the same shape again
  1. Tạo dự án và cài đặt các phần phụ thuộc của dự án bằng cách dán vào thiết bị đầu cuối:
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator
    agents-cli create visual-director \
      --agent adk \
      --agent-directory visual_director \
      --deployment-target cloud_run \
      --session-type in_memory \
      --cicd-runner skip \
      --region <YOUR_REGION> \
      --auto-approve
    cd visual-director
    agents-cli update -y && agents-cli install && uv sync --upgrade-package google-adk
    

Viết nguyên tắc sử dụng thương hiệu

  1. Tạo thư mục kỹ năng và mở tệp:
    mkdir -p visual_director/skills/brand-guidelines
    cloudshell edit visual_director/skills/brand-guidelines/SKILL.md
    
  2. Dán nội dung sau vào tệp SKILL.md đang mở trong Trình chỉnh sửa rồi lưu tệp đó:Kỹ năng
    ---
    name: brand-guidelines
    description: The house brand rules for a key visual — palette, light, composition, subject treatment, and what never appears. Load this before writing art direction or rendering an image, so the visual looks like it came from this brand rather than from a generic image model.
    ---
    
    ## Brand guidelines
    
    House style for campaign key visuals. Every rule here beats a preference in the
    brief. If the brief asks for something forbidden below, follow the brief for the
    subject and the guidelines for the treatment.
    
    ## Palette
    
    Deep indigo and slate as the ground. One warm accent — amber or terracotta —
    carrying the eye to the subject. Nothing neon, nothing fluorescent.
    
    At most three colors with any weight. A fourth reads as clutter.
    
    ## Light
    
    One dominant source, low and raking. Long shadows are wanted. Golden hour or a
    single hard studio key, never flat overhead light and never a ring light.
    
    Rim light is allowed on the subject only, and only to separate it from the
    ground.
    
    ## Composition
    
    Subject off-center, roughly a third in. Generous negative space on the opposite
    side — copy lands there later, so leave it genuinely empty.
    
    Eye level or slightly below. No top-down flat lays. No fisheye, no Dutch tilt.
    
    ## Subject
    
    One subject. If the idea implies a crowd, pick the one that carries it.
    
    Photographic realism with a shallow depth of field. Not illustration, not 3D
    render, not clay, not pixel art.
    
    ## Never
    
    - Text, logos, watermarks, or signage anywhere in the frame
    - Collage or split-screen layouts
    - Chrome, lens flare, bokeh sparkle
    - Cool-toned neon cyan or magenta
    
    ## Rendering the prompt
    
    When you turn art direction into an image prompt, state the palette, the light,
    and the composition explicitly. An image model will not infer them from the
    subject alone. Put the "Never" items in as plain description of what the frame
    does contain, not as negatives — image models handle a positive description far
    better than a list of prohibitions.
    

Cập nhật visual_director/agent.py

Cập nhật tệp agent.py và định cấu hình tệp này để có kỹ năng đã xác định trước đó.

  1. Mở tệp tác nhân:
    cloudshell edit ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director/visual_director/agent.py
    
  2. Thay thế toàn bộ nội dung tệp visual_director/agent.py bằng đoạn mã sau:
    import mimetypes
    from pathlib import Path
    
    from google import genai
    from google.adk.agents import Agent
    from google.adk.apps import App
    from google.adk.models import Gemini
    from google.adk.skills import load_skill_from_dir
    from google.adk.tools import ToolContext
    from google.adk.tools.skill_toolset import SkillToolset
    from google.genai import types
    MODEL = "gemini-3.8-flash"
    
    ## Configure the `brand-guidelines` skill for the agent
    BRAND_SKILL = load_skill_from_dir(Path(__file__).parent / "skills" / "brand-guidelines")
    
    ## Add definition of `generate_key_visual` tool below this comment
    
    root_agent = Agent(
       name="visual_director",
       model=Gemini(
          model=MODEL,
          client_kwargs={"location": "global"},
          retry_options=types.HttpRetryOptions(attempts=3),
       ),
       description="Turns a campaign concept into art direction and a key visual.",
       instruction="""You are the Visual Director on a campaign pitch team.
    
       Call `load_skill` for `brand-guidelines` before you write anything. The house
       style is not optional and it is not in this prompt.
    
       You are given a campaign concept. Then, in order:
    
       1. Write art direction for ONE key visual that sells it, obeying the brand
       guidelines: subject, composition, lighting, color, mood. Three or four
       sentences.
       2. Call `generate_key_visual` with exactly that art direction.
    
       Your final reply is the art direction itself and nothing else. No preamble, no
       alternatives, no questions back. Do not mention the skill, the tool, or the
       image file — the image travels on its own.""",
       tools=[
          SkillToolset(skills=[BRAND_SKILL]),
          ## Supply the `generate_key_visual` tool below this comment
       ],
    )
    
    app = App(
       root_agent=root_agent,
       name="visual_director",
    )
    
    Giờ đây, tác nhân visual-director đã được định cấu hình đúng cách bằng một kỹ năng của tác nhân. Trước khi kiểm thử, bạn sẽ trang bị cho tác nhân này một công cụ để tạo thành phần hình ảnh như một trong những chức năng chính của tác nhân.

5. Trang bị cho tác nhân một công cụ tạo hình ảnh

Trong nhiệm vụ này, bạn sẽ cung cấp cho Giám đốc hình ảnh một công cụ kết xuất hướng nghệ thuật dưới dạng hình ảnh và lưu hình ảnh đó dưới dạng một cấu phần phần mềm. Hiện vật là một phần dữ liệu nhị phân hoặc nội dung tệp được đặt tên và có phiên bản mà một tác nhân AI tạo, sửa đổi hoặc sử dụng trong một nhiệm vụ.

công cụ và cấu phần phần mềm mà công cụ đó ghi

Triển khai và thêm công cụ vào tác nhân

  1. Mở tệp tác nhân:
    cloudshell edit ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director/visual_director/agent.py
    
  2. Sao chép công cụ generate_key_visual sau đây trước phần định nghĩa root_agent:
    async def generate_key_visual(art_direction: str, tool_context: ToolContext) -> dict:
       """Render the art direction as the campaign's key visual.
    
       Args:
          art_direction: The art direction to render, in full.
    
       Returns:
          The saved artifact's filename, version, size and media type.
       """
       IMAGE_MODEL = "gemini-3.1-flash-lite-image"
    
       response = await genai.Client(location="global").aio.models.generate_content(
          model=IMAGE_MODEL, contents=art_direction
       )
    
       ## The image arrives as inline bytes on one of the response parts.
       image = next(
          (
             part.inline_data
             for part in response.candidates[0].content.parts or []
             if part.inline_data
          ),
          None,
       )
       if image is None:
          raise ValueError(f"{IMAGE_MODEL} returned no image for: {art_direction[:120]}")
    
       filename = "key_visual" + (
          mimetypes.guess_extension(image.mime_type or "") or ".bin"
       )
       version = await tool_context.save_artifact(filename, types.Part(inline_data=image))
    
       ## Return the artifact filename and version
       return {
          "filename": filename,
          "version": version,
          "bytes": len(image.data),
          "mime_type": image.mime_type,
       }
    
  3. Thêm generate_key_visual bên dưới SkillToolset để trang bị công cụ cho tác nhân:
       tools=[
          SkillToolset(skills=[BRAND_SKILL]),
          ## Supply the `generate_key_visual` tool below this comment
          generate_key_visual,
       ],
    
  4. agents-cli bao gồm một giao diện người dùng phát triển web tích hợp có tên là playground để kiểm tra, tương tự như ADK. Cách bắt đầu sử dụng Playground:
    agents-cli playground --port 8080
    
  5. Nhấp vào Web Preview (Xem trước trên web), biểu tượng con mắt ở trên cùng bên phải của thiết bị đầu cuối, rồi chọn Preview on port 8080 (Xem trước trên cổng 8080).nhấp vào bản xem trước trên web
  6. Bạn sẽ thấy giao diện sau đây để trò chuyện với nhân viên hỗ trợ. Hãy thử gửi câu lệnh "Chín mạng. Không trọng lực".quy trình minh hoạBạn có thể kiểm tra từ các lượt tương tác cách tác nhân visual_director thực thi load_skill("brand-guidelines") để đọc kỹ năng nguyên tắc trình bày thương hiệu, sau đó thực thi generate_key_visual để bắt đầu tạo hình ảnh. Hình ảnh thu được sẽ được lưu trữ dưới dạng một cấu phần phần mềm, đồng thời xuất hiện trong giao diện người dùng và bạn có thể dễ dàng kiểm tra.
  7. Quay lại thiết bị đầu cuối và dừng quy trình agents-cli playground đang chạy bằng cách nhấn tổ hợp phím Ctrl+C hai lần.

6. Kiến trúc nhiều tác nhân có giao thức A2A

Trong nhiệm vụ này, bạn sẽ cung cấp Visual Director dưới dạng một dịch vụ mà các tác nhân khác có thể gọi và định cấu hình dịch vụ này để phản hồi bằng hình ảnh đã tạo.

Agent2Agent (A2A) là một giao thức mở để một tác nhân gọi một tác nhân khác qua mạng. Phần trung tâm là thẻ tác nhân, một tài liệu JSON mà tác nhân xuất bản về chính nó thông qua một URL nổi tiếng, cung cấp tên, nội dung mô tả và các chức năng của tác nhân. Người gọi tìm nạp thẻ và biết cách trao đổi với tác nhân đằng sau thẻ đó mà không cần mã nguồn, các phần phụ thuộc hoặc ngôn ngữ dùng chung.

Giao thức A2A

agents-cli đã kết nối sẵn tính năng này theo mặc định. Khung được tạo visual_director/app_utils/a2a.py, tạo thẻ từ tác nhân của bạn và gắn các tuyến đường, và visual_director/fast_api_app.py, cung cấp các tuyến đường đó. Bạn chỉ cần sửa đổi một chút để cho phép nó gửi các cấu phần phần mềm trở lại máy khách.

Cấu trúc sẽ có dạng như sau. Xin lưu ý rằng tác nhân này chưa được tích hợp vào quy trình công việc ban đầu của chúng tôi:

thiết lập máy chủ A2A

Khung agents-cli máy chủ A2A

Mã và mô-đun sau đây đã được cung cấp khi chúng ta khởi chạy tác nhân visual-director bằng agents-cli. Mở các tệp này bằng cách dán các lệnh sau vào dòng lệnh:

cloudshell edit visual_director/fast_api_app.py
cloudshell edit visual_director/app_utils/a2a.py

visual_director/fast_api_app.py sẽ khởi động tác nhân và gắn các tuyến A2A trong một đường dẫn được tạo từ tên ứng dụng:

await attach_a2a_routes(
    app,
    agent=root_agent,
    runner=runner,
    rpc_path=f"/a2a/{adk_app.name}",   ## becomes /a2a/visual_director
)

visual_director/app_utils/a2a.py sẽ làm những việc còn lại trong 3 nước đi.

Trước tiên, nó tạo thẻ tác nhân từ chính tác nhân đó. Tên, nội dung mô tả và mọi công cụ đều trở thành các mục mà người gọi có thể đọc:

agent_card = await AgentCardBuilder(
    agent=agent,
    rpc_url=f"{resolved_app_url}{rpc_path}",
).build()

Thứ hai, nó đặt một trình thực thi giữa giao thức và nhân viên hỗ trợ của bạn. Các yêu cầu A2A đến được chuyển thành các lần chạy ADK và kết quả được chuyển đổi ngược lại:

request_handler = DefaultRequestHandler(
    agent_executor=A2aAgentExecutor(runner=runner, force_new_version=True),
    task_store=task_store,
    agent_card=agent_card,
)

Thứ ba, nó gắn 2 tuyến đường: một tuyến đường phục vụ thẻ, một tuyến đường chấp nhận các cuộc gọi.

Tuyến đường

Mục đích

/a2a/visual_director/.well-known/agent-card.json

Thẻ. Người gọi sẽ đọc thông tin này trước tiên để biết những việc mà tác nhân có thể làm

/a2a/visual_director

Điểm cuối JSON-RPC. Nơi các yêu cầu thực tế được gửi đến

Cho phép cấu phần phần mềm vượt qua ranh giới

Theo mặc định, phản hồi A2A sẽ có văn bản. Hình ảnh là một cấu phần phần mềm và cấu phần phần mềm sẽ nằm trong kho lưu trữ của tác nhân phân phát, trừ phi bạn định cấu hình để đưa cấu phần phần mềm đó vào.

  1. Mở tệp A2A đã tạo:
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director
    cloudshell edit visual_director/app_utils/a2a.py
    
  2. Tìm mục nhập AgentCardBuilder rồi thêm các mục nhập A2aAgentExecutorConfig và include_artifacts_in_a2a_event_interceptor bên dưới:
    ## ...
    ## from google.adk.a2a.utils.agent_card_builder import AgentCardBuilder
    ## Add the following two below `AgentCardBuilder` import
    from google.adk.a2a.executor.config import A2aAgentExecutorConfig
    from google.adk.a2a.executor.interceptors.include_artifacts_in_a2a_event import (
       include_artifacts_in_a2a_event_interceptor,
    )
    
  3. Tìm dòng agent_executor=A2aAgentExecutor(runner=runner), rồi thay thế bằng dòng sau (thêm A2aAgentExecutorConfig):
             agent_executor=A2aAgentExecutor(
                runner=runner,
                force_new_version=True,
                config=A2aAgentExecutorConfig(
                   execute_interceptors=[include_artifacts_in_a2a_event_interceptor]
                ),
             ),
    

Khởi động dịch vụ và đọc thẻ A2A đã xuất bản

  1. Khởi động máy chủ ở chế độ nền. Bạn cần khai báo APP_URL, vì đây là địa chỉ mà thẻ quảng cáo là của riêng mình. Dán nội dung này vào cửa sổ dòng lệnh:
    APP_URL=http://localhost:8801 nohup uv run uvicorn visual_director.fast_api_app:app --port 8801 > /tmp/visual-director.log 2>&1 &
    
  2. Chờ vài giây để chiến dịch bắt đầu phân phát, sau đó xác nhận rằng chiến dịch đang phân phát:
    sleep 10
    tail -2 /tmp/visual-director.log
    
    Bạn sẽ thấy:
    INFO:     Application startup complete.
    INFO:     Uvicorn running on http://127.0.0.1:8801 (Press CTRL+C to quit)
    
  3. Đọc thẻ mà nhân viên hỗ trợ hiện xuất bản:
    curl -s http://localhost:8801/a2a/visual_director/.well-known/agent-card.json | python3 -m json.tool
    
    Thẻ này liệt kê tên, nội dung mô tả, URL và các kỹ năng của trợ lý, bao gồm cả generate_key_visual. Nội dung này được tạo trực tiếp từ định nghĩa tác nhân trong mã.
    {
       "name": "visual_director",
       "description": "Turns a campaign concept into art direction and a key visual.",
       "skills": [
            {
                "id": "visual_director",
                "name": "model",
                "tags": [
                    "llm"
                ]
            },
            {
                "id": "visual_director-generate_key_visual",
                "name": "generate_key_visual",
                "description": "Render the art direction as the campaign's key visual.\n\nArgs:\n   art_direction: The art direction to render, in full.\n\nReturns:\n   The saved artifact's filename, version, size and media type.",
                "tags": [
                    "llm",
                    "tools"
                ]
            }
       ],
       "preferredTransport": "JSONRPC",
       "protocolVersion": "0.3",
       "url": "http://localhost:8801/a2a/visual_director"
    }
    
    Bạn đã sẵn sàng nhúng tác nhân visual-director vào quy trình pitch-generator chính.

7. Nhúng tác nhân từ xa A2A vào quy trình công việc

Trong nhiệm vụ này, bạn sẽ thêm tác nhân từ xa vào biểu đồ dưới dạng nhánh thứ ba.

Tác nhân từ xa được bao bọc trong RemoteA2aAgent và được đặt trong biểu đồ như mọi nút khác. Các cạnh vẫn giữ nguyên hình dạng và trình điều phối không biết nút nào là nút cục bộ.

Hướng dẫn điều phối viên về dịch vụ và cập nhật quy trình làm việc trên biểu đồ

  1. Mở tệp tác nhân:
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator
    cloudshell edit pitch_generator/agent.py
    
  2. Thêm các nội dung nhập này vào đầu tệp, cùng với các nội dung nhập hiện có:
    import asyncio
    
    import subprocess
    import google.auth.transport.requests
    import google.oauth2.id_token
    import httpx
    from google.adk.a2a.converters.part_converter import convert_genai_part_to_a2a_part
    
  3. Thêm định nghĩa RemoteA2aAgent sau đây phía trên phần khai báo creative_director:
    VISUAL_DIRECTOR_URL = os.getenv("VISUAL_DIRECTOR_URL", "http://localhost:8801")
    
    ## For deployment on Cloud Run
    def _cloud_run_client() -> httpx.AsyncClient | None:
       """An httpx client that signs each request with a Cloud Run ID token.
    
       Cloud Run deploys private, so both the agent-card fetch and the A2A calls
       need one.
       """
       if not VISUAL_DIRECTOR_URL.startswith("https://"):
          return None
    
       async def sign(request: httpx.Request) -> None:
          token = None
          try:
             token = await asyncio.to_thread(
                   google.oauth2.id_token.fetch_id_token,
                   google.auth.transport.requests.Request(),
                   VISUAL_DIRECTOR_URL,
             )
          except Exception:
             token = subprocess.check_output(
                ["gcloud", "auth", "print-identity-token", "-q"],
                stderr=subprocess.PIPE
             ).decode().strip()
          if not token:
                raise Exception("Failed to fetch ID token")
          request.headers["Authorization"] = f"Bearer {token}"
    
       return httpx.AsyncClient(event_hooks={"request": [sign]}, timeout=600)
    
    ## Keep the approval's tool traffic out of the A2A request
    def _pitch_parts_only(part: types.Part):
       """Sends the Visual Director text and media only.
    
       RemoteA2aAgent replays the session on every call, and the Visual Director
       runs downstream of the human approval, so the approval's function_response
       would travel alongside the concept text. An ADK server reads a function
       response as "resume the paused invocation" and rejects a message that also
       carries text, so drop the tool parts here.
       """
       if part.function_call or part.function_response:
          return None
       return convert_genai_part_to_a2a_part(part)
    
    visual_director = RemoteA2aAgent(
       name="visual_director",
       description="Turns a campaign concept into art direction for one key visual.",
       agent_card=(
          f"{VISUAL_DIRECTOR_URL}/a2a/visual_director{AGENT_CARD_WELL_KNOWN_PATH}"
       ),
       httpx_client=_cloud_run_client(),
       genai_part_converter=_pitch_parts_only,
    )
    
  4. Ghi đè hoặc thay thế hàm Python của nút package bằng định nghĩa sau:
    async def package(ctx: Context, node_input: dict):
       """Render the joined branches as the finished pitch."""
       empty = [name for name, value in node_input.items() if not value]
       if empty:
          raise ValueError(f"nothing reached the join from: {', '.join(empty)}")
    
       def _key_visual(ctx: Context) -> types.Blob | None:
          """The image the Visual Director sent back, or None if it sent none."""
          for event in reversed(ctx.session.events):
             if event.author != "visual_director" or not event.content:
                   continue
             for part in event.content.parts or []:
                   if part.inline_data and part.inline_data.data:
                      return part.inline_data
          return None
    
       image = _key_visual(ctx)
       if image is None:
          raise ValueError("the Visual Director returned art direction but no image")
    
       suffix = mimetypes.guess_extension(image.mime_type or "") or ".bin"
       filename = f"key_visual{suffix}"
       await ctx.save_artifact(filename, types.Part(inline_data=image))
    
       pitch = "\n\n".join(
          [
             f"CONCEPT\n{node_input['creative_director']}",
             f"COPY\n{node_input['copywriter']}",
             f"ART DIRECTION\n{node_input['visual_director']}",
             f"KEY VISUAL\n{filename}, {len(image.data)} bytes, {image.mime_type}",
          ]
       )
       yield Event(
          content=types.Content(
             role="model",
             parts=[types.Part(text=pitch), types.Part(inline_data=image)],
          )
       )
       yield Event(output=pitch)
    
  5. Thêm Cổng phê duyệt của con người trước khi quá trình tạo nội dung tiếp theo diễn ra. Trong pitch_generator/agent.py, hãy xác định các nút phê duyệt ở trên root_agent:
    @node(rerun_on_resume=False)
    async def approve_concept(ctx: Context):
        yield RequestInput(
            message="Please approve the campaign concept (yes/no).",
            response_schema=str
        )
    
    @node(rerun_on_resume=True)
    async def user_approval(ctx: Context):
       user_response = await ctx.run_node(approve_concept)
       if str(user_response).lower() in ("yes", "y"):
          approved_concept = f"## Approved Concept\n\n{ctx.session.state['creative_director']}"
          yield Event(
             content=types.Content(
                role="model",
                parts=[types.Part(text=approved_concept)]
             )
          )
       else:
          raise ValueError("User rejected the concept")
    
  6. Cuối cùng, hãy cập nhật các nội dung nhập ở trên cùng để thêm RequestInput và node từ google.adk.events và google.adk.workflow:
    from google.adk.events import Event, RequestInput
    from google.adk.workflow import JoinNode, Workflow, node
    
    và sửa đổi quy trình root_agent edges để định tuyến qua user_approval:
        edges=[
            ("START", creative_director),
            (creative_director, user_approval, (copywriter, visual_director)),
            ((creative_director, copywriter, visual_director), assemble),
            (assemble, package),
        ],
    
    Một số thông tin chi tiết đáng chú ý:
    • Phê duyệt có sự tham gia của con người (HITL): approve_concept sẽ tạo ra RequestInput. Khi quy trình công việc chạm đến nút này, quá trình thực thi sẽ tạm dừng và phát ra một tín hiệu ngắt cho người gọi. Các nhánh hạ lưu (copywriter và visual_director) sẽ không chạy cho đến khi người dùng xác nhận phê duyệt.
    • visual_director xuất hiện ở các cạnh giống hệt như copywriter, mặc dù một cạnh là lệnh gọi hàm và cạnh kia nằm trên mạng.
    • Hình ảnh được trả về sẽ được đọc từ các sự kiện của phiên thay vì từ node_input, vì hiện tại chỉ có văn bản được chuyển thành đầu ra của một nút. Hình ảnh đã vượt qua ranh giới thông qua trình chặn A2A, nhưng được đính kèm vào các sự kiện của tác nhân từ xa, vì vậy hàm trợ giúp _key_visual sẽ truy xuất hình ảnh từ đó.

Kiểm thử quá trình tích hợp

Bây giờ, bạn sẽ kiểm thử để đảm bảo Visual Director (chạy trong nền) và Pitch Generator có thể giao tiếp thông qua giao thức A2A.

  1. Xác nhận rằng Visual Director vẫn đang chạy ở chế độ nền:
    curl -s -o /dev/null -w "visual director: %{http_code}\n" http://localhost:8801/a2a/visual_director/.well-known/agent-card.json
    
    Bạn sẽ nhận được kết quả visual director: 200 tại đây.Trong trường hợp bạn nhận được trạng thái khác, hãy chạy lệnh sau để khởi động lại:
    fuser -k 8801/tcp 2>/dev/null || true
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director
    APP_URL=http://localhost:8801 nohup uv run uvicorn visual_director.fast_api_app:app --port 8801 > /tmp/visual-director.log 2>&1 &
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator
    sleep 10
    curl -s -o /dev/null -w "visual director: %{http_code}\n" http://localhost:8801/a2a/visual_director/.well-known/agent-card.json
    
  2. Bắt đầu xem trước:
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator/pitch_generator
    agents-cli playground --port 8080
    
  3. Nhấp vào Web Preview (Xem trước trên web), biểu tượng con mắt ở trên cùng bên phải của thiết bị đầu cuối, rồi chọn Preview on port 8080 (Xem trước trên cổng 8080).nhấp vào bản xem trước trên web
  4. Bạn sẽ thấy giao diện để trò chuyện với tác nhân. Hãy thử gửi câu lệnh "Ván trượt bay cho mèo".quy trình minh hoạquy trình minh hoạBạn sẽ thấy toàn bộ hoạt động tương tác, trong đó quy trình công việc bắt đầu và mỗi tác nhân chạy. Sau khi creative_director phác thảo ý tưởng, quy trình làm việc sẽ tạm dừng ở bước phê duyệt và yêu cầu bạn xác nhận: "Vui lòng phê duyệt ý tưởng về chiến dịch (có/không)." Nhập "yes" vào trường nhập dữ liệu để tiếp tục. Sau khi được phê duyệt, biểu đồ sẽ mở rộng: bạn có thể thấy thời điểm tác nhân visual-director bắt đầu tải kỹ năng và ở giữa quá trình tạo hình ảnh, tác nhân copywriter hoàn tất việc tạo chú thích trên mạng xã hội. Điều này cho thấy cả hai tác nhân đều chạy song song mà không cần chờ đợi lẫn nhau. Sau khi cả hai hoàn tất, nút assemble sẽ kết hợp các kết quả với khái niệm đã được phê duyệt do creative_director tạo ra, hoàn tất việc phân phối nội dung đề cử trong hàm package.hai dịch vụ này, được kết nối qua A2A

8. Lưu trữ hình ảnh chính trong Cloud Storage

Cho đến nay, mọi hình ảnh chính đều nằm trong bộ nhớ. Khi quá trình này dừng lại, hình ảnh sẽ biến mất. Điều này phù hợp với một sân chơi và vô ích cho một chiến dịch. Trong nhiệm vụ này, bạn sẽ cung cấp cho quy trình một bộ chứa để ghi vào và tạo một tập dữ liệu BigQuery mà bạn sẽ sử dụng trong nhiệm vụ cuối cùng.

ADK quyết định vị trí của các cấu phần phần mềm khi khởi động. app_utils/services.py được tạo sẽ đọc một biến môi trường duy nhất:

def get_artifact_service():
    """Process-wide artifact service: GCS when a bucket is set, else in-memory."""
    if bucket := os.environ.get("LOGS_BUCKET_NAME"):
        return GcsArtifactService(bucket_name=bucket)
    return InMemoryArtifactService()

Nếu bạn đặt LOGS_BUCKET_NAME, mọi lệnh gọi save_artifact sẽ được chuyển vào Cloud Storage.

tích hợp dịch vụ tạo tác với bộ chứa GCS

Tạo tập dữ liệu BigQuery

  1. Trong Cloud Shell, hãy chạy lệnh sau để tạo một tập dữ liệu BigQuery:
    bq mk --dataset --location=<YOUR_REGION> --project_id=<YOUR_PROJECT_ID> bwg
    
  2. Trỏ kho lưu trữ cấu phần phần mềm của trình điều phối vào nhóm:
    export LOGS_BUCKET_NAME="<YOUR_PROJECT_ID>-bwg"
    
    GcsArtifactService lấy tên vùng chứa trần, không có tiền tố gs://. Giống như các biến khác, biến này nằm trong shell của bạn và tác vụ triển khai sẽ truyền biến này đến Cloud Run.
  3. Kiểm tra nội dung trong vùng lưu trữ. Trong một thẻ trình duyệt mới, hãy mở URL sau:
    https://console.cloud.google.com/storage/browser?project=<YOUR_PROJECT_ID>
    
    Nhấp vào vùng chứa. Tuỳ thuộc vào các thử nghiệm trước đây của bạn, phần này có thể chứa những hình ảnh đã được tạo trước đó.

9. Triển khai tác nhân Visual Director lên Cloud Run

Cho đến nay, mọi thứ đều chạy trên một máy, với Giám đốc hình ảnh có thể liên hệ tại localhost:8801. Trong nhiệm vụ này, bạn sẽ triển khai tác nhân Visual Director dưới dạng dịch vụ Cloud Run riêng và cho phép trình tạo bản đề cử giao tiếp với tác nhân này trên mạng bằng giao thức Agent2Agent (A2A).

Visual Director nhận được một URL https:// thực, vì vậy _cloud_run_client mà bạn đã viết trước đó sẽ bắt đầu ký các yêu cầu thay vì trả về None. Ngoài ra, Cloud Run triển khai ở chế độ riêng tư theo mặc định.

cả hai tác nhân dưới dạng dịch vụ Cloud Run

Triển khai Visual Director

  1. Dừng dịch vụ nền cục bộ và hộp cát nếu chúng vẫn đang chạy:
    fuser -k 8801/tcp 2>/dev/null || true
    fuser -k 8000/tcp 2>/dev/null || true
    
  2. Triển khai tác nhân. Thao tác này sẽ tạo một vùng chứa và đẩy vùng chứa đó.⏰ Quá trình này sẽ mất vài phút.
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director
    source .env
    agents-cli deploy \
      --service-name visual-director \
      --project <YOUR_PROJECT_ID> \
      --region <YOUR_REGION> \
      --no-confirm-project \
      --labels dev-tutorial=bwg001-devlab-1 \
      --update-env-vars "LOGS_BUCKET_NAME=$LOGS_BUCKET_NAME,GOOGLE_GENAI_USE_VERTEXAI=true,GOOGLE_CLOUD_PROJECT=$GOOGLE_CLOUD_PROJECT,GOOGLE_CLOUD_LOCATION=$GOOGLE_CLOUD_LOCATION,GOOGLE_CLOUD_REGION=$GOOGLE_CLOUD_REGION"
    

Chạy Trình tạo bản đề cử sử dụng Đạo diễn hình ảnh được triển khai trên Cloud Run

  1. Đọc URL của Visual Director và chạy Pitch Generator sử dụng URL đó:
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator
    source .env
    export VISUAL_DIRECTOR_URL=$(gcloud run services list \
      --project <YOUR_PROJECT_ID> \
      --region <YOUR_REGION> \
      --flatten='spec.template.spec.containers[0].env' \
      --filter='metadata.name=visual-director AND spec.template.spec.containers[0].env.name=APP_URL' \
      --format='value(spec.template.spec.containers[0].env.value)')
    echo "Visual Director is at $VISUAL_DIRECTOR_URL"
    APP_URL=http://localhost:8000 nohup uv run uvicorn pitch_generator.fast_api_app:app --port 8000 > /tmp/visual-pitch_generator.log 2>&1 &
    

Gọi quy trình công việc đã triển khai

Cả hai tác nhân đều hỗ trợ giao thức A2A. Kho lưu trữ này cung cấp một ứng dụng nhỏ để gọi Trình tạo bản đề cử thông qua A2A.

Bạn cần đặt PITCH_GENERATOR_URL thành URL của Pitch Generator đang chạy cục bộ. Trong khi đó, Visual Director chạy trong Cloud Run và Pitch Generator giao tiếp với Visual Director qua A2A.

  1. Sao chép ứng dụng vào dự án của bạn rồi kích hoạt ứng dụng đó:
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator
    cp ../solution/pitch-generator/call_agent.py .
    PITCH_GENERATOR_URL=http://localhost:8000 uv run python call_agent.py "Flying skateboards for cats" --save-image ./key_visual.png
    
    Vì quy trình này có Cổng phê duyệt của con người, nên tập lệnh sẽ tạm dừng sau khi tạo khái niệm nháp:
    === HUMAN APPROVAL REQUIRED ===
    
    [DRAFT CONCEPT]
    Concept Line: "Gravity is a Suggestion."
    ...
    
    Please approve the campaign concept (yes/no):
    
    Nhập yes rồi nhấn Enter để phê duyệt và tiếp tục quy trình. (Bạn cũng có thể truyền -y hoặc --auto-approve để tự động phê duyệt).
  2. Mở call_agent.py và kiểm tra cách hoạt động này tương tác với tác nhân bằng cách sử dụng a2a-sdk:
    • ClientFactory xây dựng một A2A chính thức Client nhắm đến https://SERVICE_URL/a2a/pitch_generator.
    • generate_pitch_async gửi một SendMessageRequest ban đầu. Khi phản hồi trả về với TaskState.TASK_STATE_INPUT_REQUIRED, phản hồi sẽ phát hiện phần adk_request_input, hiển thị khái niệm nháp, thu thập sự phê duyệt của bạn và gửi yêu cầu tiếp tục kèm theo phản hồi của bạn trở lại nhiệm vụ.
    • Vòng lặp phân tích cú pháp sẽ đọc các phần CONCEPT, COPY và ART DIRECTION từ các cấu phần phần mềm của tác vụ đã hoàn tất, đồng thời lưu hình ảnh nhị phân vào key_visual.png.
    Bạn sẽ thấy 3 phần văn bản được in và hình ảnh được lưu vào key_visual.png.
  3. Tải hình ảnh chính lên bộ chứa của bạn, trong một tiền tố mà bạn có thể trỏ BigQuery đến:
    gcloud storage cp ./key_visual.png gs://<YOUR_PROJECT_ID>-bwg/key-visuals/cats.png
    
  4. Tạo một bản đề cử thứ hai để bạn có thể so sánh và tải bản đề cử đó lên:
    PITCH_GENERATOR_URL=http://localhost:8000 uv run python call_agent.py "A commuter bike built for rainy cities" --save-image ./bike.png
    gcloud storage cp ./bike.png gs://<YOUR_PROJECT_ID>-bwg/key-visuals/bike.png
    

10. Chấm điểm hình ảnh chính để phù hợp với thương hiệu trong BigQuery

Giờ đây, bạn đã có hình ảnh trong một nhóm và các nguyên tắc sử dụng thương hiệu được cho là sẽ định hình những hình ảnh đó. Trong nhiệm vụ này, bạn sẽ kết hợp cả hai: tham chiếu hình ảnh từ BigQuery, sau đó yêu cầu một mô hình Gemini chấm điểm từng hình ảnh dựa trên các nguyên tắc mà không di chuyển một byte nào ra khỏi Cloud Storage.

Hai tính năng của BigQuery sẽ thực hiện việc này:

  • ObjectRef là một giá trị trỏ đến một đối tượng Cloud Storage. OBJ.MAKE_REF tạo một đối tượng từ URI và một kết nối cấp cho BigQuery quyền truy cập uỷ quyền vào nhóm. pitch-connection do tập lệnh thiết lập tạo ra là kết nối đó và đã có Storage Object Viewer và Agent Platform User.
  • AI.SCORE nhận một câu lệnh và trả về một FLOAT64. Câu lệnh có thể kết hợp các chuỗi với giá trị ObjectRef, vì vậy mô hình sẽ thấy cả tiêu chí chấm điểm và hình ảnh.

BigQuery đọc vùng lưu trữ

Tạo bảng hình ảnh chính

  1. Mở BigQuery Studio trong một thẻ trình duyệt mới:
    https://console.cloud.google.com/bigquery?project=<YOUR_PROJECT_ID>
    
  2. Nhấp vào Tạo truy vấn mới (biểu tượng +), sau đó dán và Chạy truy vấn sau (thay thế và bằng mã dự án và khu vực của bạn). Thao tác này sẽ tạo một hàng cho mỗi hình ảnh chính, kết hợp ý tưởng của chiến dịch với thông tin tham khảo về hình ảnh:
    SET @@location = '<YOUR_REGION>';
    
    CREATE OR REPLACE TABLE bwg.key_visuals AS
    SELECT
      campaign,
      concept,
      OBJ.FETCH_METADATA(
        OBJ.MAKE_REF(uri, '<YOUR_REGION>.pitch-connection')
      ) AS key_visual
    FROM UNNEST([
      STRUCT(
        'cats' AS campaign,
        'Flying skateboards for cats' AS concept,
        'gs://<YOUR_PROJECT_ID>-bwg/key-visuals/cats.png' AS uri
      ),
      STRUCT(
        'bike',
        'A commuter bike built for rainy cities',
        'gs://<YOUR_PROJECT_ID>-bwg/key-visuals/bike.png'
      )
    ]);
    
    bqOBJ.MAKE_REF tạo mối tham chiếu từ URI và kết nối. OBJ.FETCH_METADATA sau đó liên hệ với Bộ nhớ đám mây và điền kích thước, loại nội dung và thế hệ của đối tượng. Lệnh gọi thứ hai đó cũng là cách nhanh nhất để chứng minh rằng kết nối của bạn hoạt động: nếu quyền không chính xác, trường details sẽ trả về lỗi thay vì siêu dữ liệu.
  3. Kiểm tra những nội dung bạn đã lưu trữ. Thay thế truy vấn trước đó bằng truy vấn này rồi chạy truy vấn:
    SELECT campaign, concept, key_visual.uri, key_visual.details
    FROM bwg.key_visuals;
    
    Bảng này chứa con trỏ và siêu dữ liệu của con trỏ, chứ không chứa hình ảnh. Các byte chưa bao giờ rời khỏi nhóm.

Đánh giá từng hình ảnh dựa trên nguyên tắc sử dụng thương hiệu

Đạo diễn hình ảnh được yêu cầu tuân thủ bảng màu, quy tắc ánh sáng và quy tắc bố cục. Việc có thực sự làm như vậy hay không là một quyết định mang tính phán đoán và đó là mục đích của AI.SCORE.

  1. Chạy truy vấn sau (thay bằng khu vực của bạn). Câu lệnh là một bộ ba: tiêu chí chấm điểm, ý tưởng chiến dịch và ObjectRef cho hình ảnh:
    SELECT
      campaign,
      concept,
      AI.SCORE(
        ('''You are the brand guardian reviewing a campaign key visual.
    Score the attached image from 1 to 10 on how closely it follows these house rules:
    - Palette: deep indigo and slate, with one warm accent of amber or terracotta. Nothing neon.
    - Light: one dominant low, raking source with long shadows. No flat overhead light.
    - Composition: subject off-center with generous empty negative space on the opposite side.
    - Subject: one subject only, photographic realism, shallow depth of field.
    - Never: text, logos, or watermarks anywhere in the frame.
    A 10 obeys every rule. A 1 ignores them. The campaign concept is: ''',
          concept,
          key_visual),
        connection_id => '<YOUR_REGION>.pitch-connection',
        endpoint => 'gemini-3.5-flash'
      ) AS brand_fit
    FROM bwg.key_visuals
    ORDER BY brand_fit DESC;
    
  2. Chuyển điểm số thành kết quả mà nhóm của bạn có thể hành động:
    CREATE OR REPLACE TABLE bwg.brand_review AS
    SELECT
      campaign,
      concept,
      brand_fit,
      IF(brand_fit >= 7, 'on brand', 'needs another pass') AS verdict
    FROM (
      SELECT
        campaign,
        concept,
        AI.SCORE(
          ('''Score this campaign key visual from 1 to 10 on how closely it follows the house
    brand rules: deep indigo and slate palette with a single warm accent and nothing neon; one
    low raking light source with long shadows; the subject off-center with empty negative space
    opposite; a single photographic subject; and no text, logos, or watermarks in the frame.
    The campaign concept is: ''',
              concept,
              key_visual),
          connection_id => '<YOUR_REGION>.pitch-connection',
          endpoint => 'gemini-3.5-flash'
        ) AS brand_fit
      FROM bwg.key_visuals
    );
    
    SELECT * FROM bwg.brand_review ORDER BY brand_fit DESC;
    
    AI.SCORE chỉ trả về FLOAT64, vì vậy ngưỡng nằm trong SQL thuần tuý, nơi bạn có thể điều chỉnh ngưỡng mà không cần chỉnh sửa câu lệnh. Chấm điểm theo từng hàng, tức là cùng một tiêu chí chấm điểm được áp dụng cho mọi thành phần mà một chiến dịch tạo ra, chứ không chỉ hai thành phần mà bạn đã tạo ở đây.

11. Các bước tiếp theo

Tạo bản đánh giá

Đánh giá dựa trên tác nhân hay còn gọi là "đánh giá" là các kiểm thử tự động được dùng để đo lường mức độ hiệu quả của các tác nhân AI tự động trong việc suy luận, lập kế hoạch, sử dụng công cụ và hoàn thành quy trình công việc nhiều bước. Các kiểm thử này tương tự như kiểm thử đơn vị cho các tác nhân, nhưng có tính đến bản chất không xác định của chúng.

Một trong các tác nhân của chúng tôi có chứa Human in the loop, tức là tác nhân đó sẽ chờ dữ liệu đầu vào. Bạn sẽ cần cho phép Antigravity sửa đổi mã hiện tại để hành vi này không cản trở các quy trình đánh giá tự động.

Một trong những cách tốt nhất để bắt đầu là hỏi Antigravity CLI.

Nhập nội dung sau vào Cloud Shell để khởi động CLI:

cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/
agy

Sau đây là một câu lệnh bắt đầu:

Explore this repository (`pitch-generator` and `visual-director`), including their agent instructions, tools, and `SKILL.md` files. Design and implement a creative `agents-cli eval` evaluation suite under `tests/eval/` that stress-tests the agent's behavior.

Bạn cũng có thể tìm thấy ví dụ về các quy trình đánh giá và hướng dẫn chạy các quy trình này trong kho lưu trữ được sao chép.

12. Dọn dẹp

Để tránh bị tính phí cho tài khoản Google Cloud của bạn đối với các tài nguyên được dùng trong lớp học lập trình này, hãy xoá dự án (nên dùng) hoặc xoá từng tài nguyên được tạo trong lớp học lập trình:

gcloud run services delete visual-director --region=<YOUR_REGION> --project=<YOUR_PROJECT_ID> --quiet
bq rm -r -f -d <YOUR_PROJECT_ID>:bwg
bq rm --connection --location=<YOUR_REGION> --project_id=<YOUR_PROJECT_ID> pitch-connection
gcloud storage rm -r gs://<YOUR_PROJECT_ID>-bwg

13. Xin chúc mừng!

Trong lớp học lập trình này, bạn đã xây dựng một trình tạo bản đề xuất chiến dịch đa tác nhân bằng Bộ công cụ phát triển tác nhân. Bạn đã kết nối 3 tác nhân vào một quy trình công việc đồ thị xác định, đóng gói nguyên tắc sử dụng thương hiệu dưới dạng một kỹ năng của tác nhân tải theo yêu cầu và cung cấp cho tác nhân một công cụ tạo hình ảnh lưu kết quả đầu ra dưới dạng một cấu phần phần mềm. Sau đó, bạn chia Visual Director thành dịch vụ A2A riêng, nhúng lại vào biểu đồ dưới dạng một nút từ xa, triển khai nút đó vào Cloud Run và gọi toàn bộ quy trình công việc qua A2A từ một trình điều phối đang chạy cục bộ. Cuối cùng, bạn đã tham chiếu các hình ảnh chính được tạo từ BigQuery bằng ObjectRef và sử dụng AI.SCORE để đánh giá các hình ảnh đó dựa trên nguyên tắc sử dụng thương hiệu của riêng bạn.