Xây dựng tác nhân AI bằng ADK: Điều phối quy trình công việc dạng đồ thị

1. Tổng quan

Để đáp ứng một yêu cầu du lịch có nhiều điều kiện ràng buộc (chẳng hạn như lên kế hoạch cho một chuyến đi trong ngày trị giá 100 USD ở Kyoto với các ngôi đền lịch sử, mì ramen địa phương và không có ART Museum hiện đại), mô hình cần trích xuất các lựa chọn ưu tiên có cấu trúc, nghiên cứu các địa điểm cùng lúc, tổng hợp một bản nháp chi tiết và kiểm tra tổng chi phí trước khi phản hồi. Trong ADK 1.x, việc điều phối các giai đoạn đó đòi hỏi phải lồng các trình bao bọc SequentialAgent, ParallelAgent và LoopAgent, lưu mọi kết quả trung gian vào trạng thái phiên và gọi thêm một lệnh gọi công cụ LLM chỉ để thoát khỏi vòng lặp tinh chỉnh.

Trong lớp học lập trình này, bạn sẽ xây dựng một công cụ lập kế hoạch du lịch đa tác nhân trên Nền tảng tác nhân bằng cách sử dụng quy trình công việc dạng biểu đồ (Workflow) của Bộ công cụ phát triển tác nhân (ADK) 2. Qua ba giai đoạn tiến triển, bạn sẽ kết nối các nút Agent chuyên biệt (dựa trên gemini-3.8-flash) trong một chuỗi cạnh tuần tự (node_input), phân tán các nhà nghiên cứu google_search đồng thời được đồng bộ hoá với JoinNode, đồng thời thực thi các quy tắc về ngân sách và loại trừ bằng một bộ định tuyến Python tất định (Event(route=...)) bỏ qua bước tinh chỉnh bất cứ khi nào bản nháp ban đầu vượt qua quy trình kiểm tra.

Điều kiện tiên quyết

Kiến thức bạn sẽ học được

  • Cách khai báo quy trình đa tác nhân trong ADK 2 bằng cách sử dụng bộ dữ liệu cạnh Workflow
  • Cách ADK 2 truyền trực tiếp kết quả đầu ra dọc theo các cạnh dưới dạng node_input và liên kết các khoá trạng thái phiên dưới dạng tham số hàm
  • Cách phân nhánh các nhánh nghiên cứu đồng thời và hợp nhất kết quả bằng JoinNode
  • Cách định tuyến các chu kỳ người tạo-người kiểm tra có giới hạn một cách tất định trong Python bằng Event(route=...)
  • Cách kiểm tra biểu đồ quy trình công việc đã biên dịch, tiến trình thực thi song song và delta trạng thái trong Giao diện người dùng web của ADK

Bạn cần có

  • Tài khoản Google (sử dụng tài khoản @gmail.com cá nhân nếu bạn muốn nhận tín dụng cho hội thảo)
  • Đường liên kết nhận tín dụng cho hội thảo do người hướng dẫn cung cấp (cùng với camera điện thoại để quét mã QR) hoặc một tài khoản thanh toán trên Cloud đang hoạt động
  • Google Chrome (bạn nên dùng cửa sổ ẩn danh để tránh nhầm lẫn giữa tài khoản cá nhân và tài khoản công việc)

2. Nhận khoản tín dụng Google Cloud (Không bắt buộc)

Nếu bạn đang tham dự một hội thảo có đường liên kết nhận tín dụng cho sự kiện, hãy nhận tín dụng Google Developer Program để cấp tài khoản thanh toán mà không cần thẻ tín dụng.

Nhận khoản tín dụng cho sự kiện

  1. Mở đường liên kết nhận tín dụng hội thảo trong cửa sổ Ẩn danh rồi đăng nhập bằng tài khoản @gmail.com của bạn.
  2. Nhấp vào Nhận tín dụng trên trang Nhận lợi ích:

Thẻ Nhận lợi ích của bạn trên Trung tâm Nhà phát triển của Google, trong đó có nút Nhận tín dụng

  1. Nếu được nhắc tạo hồ sơ Google Developer Program, hãy hoàn tất biểu mẫu.
  2. Khi thông báo Quét để xác minh bạn là người thật xuất hiện, hãy quét mã QR bằng điện thoại và hoàn tất quy trình xác minh (làm mới và nhấp lại vào Nhận tín dụng nếu mã QR không xuất hiện):

Hộp thoại mã QR "Quét để xác minh bạn là người thật" của Google Developer Program

  1. Xác nhận thông báo Đã nhận thành công khoản tín dụng cho sự kiện! xuất hiện, sau đó nhấp vào Mở tài khoản thanh toán:

Trung tâm Nhà phát triển của Google cho thấy thông báo "Đã nhận thành công khoản tín dụng cho sự kiện" và nút "Mở tài khoản thanh toán"

🔍 Kiểm tra bài làm

Quá trình thiết lập tài khoản thanh toán mất tối đa 3 phút:

  1. Mở Google Cloud Billing (hoặc sao chép URL vào cửa sổ ẩn danh) rồi chọn Tài khoản thanh toán của bạn:
https://console.cloud.google.com/billing
  1. Làm mới cho đến khi tài khoản GDP Credit của bạn có trạng thái Đang hoạt động:

Trang quản lý tài khoản thanh toán trên Google Cloud cho thấy một tài khoản thanh toán có khoản tín dụng GDP đang hoạt động

3. Tạo một dự án trên Google Cloud (Không bắt buộc)

Tạo một dự án Google Cloud chuyên biệt để tất cả tài nguyên trong lớp học lập trình này được tách biệt và dễ dọn dẹp.

  1. Mở trang Tạo dự án (hoặc sao chép URL vào cửa sổ ẩn danh):
https://console.cloud.google.com/projectcreate
  1. Nhập thông tin chi tiết về dự án:
    • Tên dự án: Ví dụ: google-cloud-workshop
    • Tài khoản thanh toán (nếu có): Chọn GDP Credit (hoặc tài khoản thanh toán đang hoạt động của bạn)
    • Tài nguyên gốc: Không có tổ chức (hoặc chọn tổ chức của bạn nếu cần)
  2. Ghi lại Mã dự án được tạo bên dưới tên dự án, sau đó nhấp vào Tạo:

Màn hình Tạo dự án trên Google Cloud cho thấy Tên dự án, Mã dự án, Tài nguyên gốc và nút Tạo

🔍 Kiểm tra bài làm

Nhấp vào Notifications (Thông báo) trong thanh công cụ trên cùng bên phải rồi nhấp vào Select Project (Chọn dự án) sau khi tạo xong dự án:

Trình đơn Thông báo trên Google Cloud Console cho thấy đường liên kết Tạo dự án và Chọn dự án

4. Thiết lập môi trường

Định cấu hình Google Cloud Shell, bật Agent Platform API và tạo một môi trường ảo Python 3.12 tách biệt bằng uv.

Định cấu hình Cloud Shell

  1. Nhấp vào Activate Cloud Shell (Kích hoạt Cloud Shell) (>_) trên thanh công cụ của Google Cloud Console hoặc mở trực tiếp Google Cloud Shell (hoặc sao chép URL vào cửa sổ ẩn danh):
https://shell.cloud.google.com/?show=ide,terminal

Nút Kích hoạt Cloud Shell trên thanh công cụ của Cloud Console

  1. Nhấp vào Authorize (Uỷ quyền) nếu được nhắc:

Hộp thoại uỷ quyền Cloud Shell

  1. Chọn Mã dự án Google Cloud đang hoạt động trong trình đơn được đánh số trong dòng lệnh:
unset CLOUDSDK_CORE_PROJECT
PS3="Select your project (e.g. 1): "
select PROJECT_ID in $(gcloud projects list --format="value(projectId)"); do
  [ -n "$PROJECT_ID" ] && gcloud config set project "$PROJECT_ID" && break
done

Bạn sẽ thấy kết quả tương tự như sau (các dự án chưa được gắn thẻ cũng in một dòng thông tin [environment: untagged]):

1) google-cloud-workshop-4210
2) my-cloud-project-8812
Select your project (e.g. 1): 1
Updated property [core/project].

Xác minh rằng Mã dự án bạn chọn xuất hiện được đánh dấu bằng màu vàng trong lời nhắc của Cloud Shell:

Thiết bị đầu cuối Cloud Shell cho thấy mã dự án đã chọn được đánh dấu bằng màu vàng

Bật các API bắt buộc

Bật Agent Platform API để các tác nhân ADK có thể gọi các mô hình Gemini:

gcloud services enable aiplatform.googleapis.com

Bạn sẽ thấy kết quả tương tự như sau:

Operation "operations/acat.p2-..." finished successfully.

Tạo môi trường ảo Python và cài đặt ADK

Tạo không gian làm việc ~/adk-graph-agents và kích hoạt môi trường ảo Python 3.12:

cd ~
mkdir -p adk-graph-agents && cd ~/adk-graph-agents

which uv >/dev/null 2>&1 || curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"

uv venv .venv --python 3.12
source .venv/bin/activate

Xác nhận tiền tố (.venv) xuất hiện ở đầu lời nhắc của thiết bị đầu cuối:

Cửa sổ dòng lệnh Cloud Shell cho thấy tiền tố .venv trên một môi trường ảo Python đang hoạt động

Cài đặt ADK 2 trong môi trường ảo đang hoạt động:

uv pip install --no-cache "google-adk==2.14.0"

🔍 Kiểm tra bài làm

Xác minh phiên bản adk CLI đã cài đặt:

adk --version

Bạn sẽ thấy kết quả tương tự như sau:

adk, version 2.14.0

5. Tạo khung cho công cụ lập kế hoạch du lịch

Tạo gói travel_planner khởi đầu bằng adk create và kiểm thử cơ sở tác nhân đơn mặc định trong adk web.

Tạo gói khởi đầu

Chạy adk create từ ~/adk-graph-agents:

cd ~/adk-graph-agents
adk create travel_planner

Chọn các tuỳ chọn sau khi được nhắc (nếu được nhắc bằng Enable telemetry? [Y/n]: trong lần chạy đầu tiên, hãy nhấn phím Enter hoặc nhập n rồi nhấn phím Enter):

  1. Chọn một mô hình cho tác nhân gốc: Nhập 2 để chọn gemini-3.8-flash (lựa chọn 1 chọn gemini-3.5-flash) rồi nhấn phím Enter.
  2. Chọn một phần phụ trợ: Nhập 2 để chọn phần phụ trợ Nền tảng tác nhân của Google Cloud (mục 2 trong trình đơn CLI bên dưới) rồi nhấn phím Enter.
  3. Nhập mã dự án trên đám mây của Google: Nhấn phím Enter để chấp nhận Mã dự án được phát hiện trong dấu ngoặc.
  4. Enter Google Cloud region (Nhập khu vực Google Cloud): Nhập global (để sử dụng điểm cuối Gemini toàn cầu) rồi nhấn Enter.

adk create ghi GOOGLE_GENAI_USE_ENTERPRISE=1, mã dự án của bạn và GOOGLE_CLOUD_LOCATION=global vào travel_planner/.env, nhờ đó thông tin xác thực của bạn vẫn được giữ nguyên khi bạn thay thế agent.py sau này.

Bạn sẽ thấy kết quả tương tự như sau:

Choose a model for the root agent:
1. gemini-3.5-flash
2. gemini-3.8-flash
3. Other models (fill later)
Choose model (1, 2, 3): 2
1. Google AI
2. Vertex AI
3. Login with Google
Choose a backend (1, 2, 3): 2

You need an existing Google Cloud account and project, check out this link for details:
https://google.github.io/adk-docs/get-started/quickstart/#gemini---google-cloud-vertex-ai

Enter Google Cloud project ID [google-cloud-workshop-4210]:
Enter Google Cloud region [us-central1]: global

Agent created in /home/your_username/adk-graph-agents/travel_planner:
- .env
- .gitignore
- __init__.py
- agent.py

⚠️  WARNING: Secrets (like GOOGLE_API_KEY) are stored in .env.

Next steps:
- Chat with your agent in the terminal: adk run travel_planner
- Or in the browser: adk web .
- Have your coding agent evaluate and deploy it: uvx google-agents-cli setup
  (other install options: https://adk.dev/get-started/agents-cli/)

Mở ~/adk-graph-agents trong trình soạn thảo mã Cloud Shell:

which cloudshell >/dev/null 2>&1 && cloudshell workspace ~/adk-graph-agents || true

Kích hoạt lại môi trường ảo trong thiết bị đầu cuối đã tải lại:

cd ~/adk-graph-agents
source .venv/bin/activate

Kiểm thử tác nhân đơn lẻ khởi đầu

Khởi động Giao diện người dùng web của ADK để quan sát cách một tác nhân khởi đầu không được điều phối trả lời câu lệnh về du lịch:

cd ~/adk-graph-agents
adk web --allow_origins "*"

Nhấp vào biểu tượng Web Preview (Xem trước trên web) trong thanh công cụ Cloud Shell ở trên cùng bên phải, chọn Change Port (Thay đổi cổng), nhập 8000 rồi nhấp vào Change and Preview (Thay đổi và xem trước):

Hộp thoại cấu hình cổng Xem trước trên web của Cloud Shell được đặt thành 8000

🔍 Kiểm tra bài làm

Chọn travel_planner trong trình đơn thả xuống trên cùng bên trái của adk web rồi gửi "Help me plan a trip to Japan":

Giao diện người dùng web phát triển ADK cho thấy một cuộc trò chuyện cơ bản với một tác nhân

Bạn sẽ thấy một câu trả lời trò chuyện một lượt trong ngăn trò chuyện (với một lệnh gọi root_agent duy nhất trong Sự kiện):

root_agent -> responds in one pass without structured preference extraction, live search, or budget verification

Nhấn Ctrl + C trong thiết bị đầu cuối Cloud Shell để dừng adk web trước khi chỉnh sửa tệp ở bước tiếp theo.

6. Xây dựng quy trình công việc biểu đồ tuyến tính

Tạo ADK 2 Workflow đầu tiên: một chuỗi tuần tự kết nối điểm truy cập "START" dành riêng với user_profiler_agent và itinerary_synthesizer_agent (bất kỳ nút nào không có cạnh đi ra đều đóng vai trò là nút cuối). Mỗi cạnh (a, b) tự động chạy các nút Agent (bí danh cho LlmAgent) ở chế độ single_turn tách biệt và phân phối kết quả của nút a dưới dạng node_input (tin nhắn của người dùng đến) cho nút b, trong khi {user_preferences} trong chuỗi lệnh nội suy khoá trạng thái phiên phù hợp được lưu thông qua output_key.

Sơ đồ cấu trúc cho thấy travel_workflow (Quy trình công việc) kết nối START với user_profiler_agent và itinerary_synthesizer_agent

Tạo các chuyên gia ban đầu

Đảm bảo adk web đã dừng (Ctrl + C), sau đó mở một tệp mới có tên là travel_planner/specialists.py:

cloudshell edit ~/adk-graph-agents/travel_planner/specialists.py

Dán mã sau vào travel_planner/specialists.py để xác định user_profiler_agent và itinerary_synthesizer_agent (nhập google_search trước cho bước tiếp theo):

from google.adk import Agent
from google.adk.tools import google_search

MODEL = "gemini-3.8-flash"

# --- Specialist 1: The User Profiler ---
user_profiler_agent = Agent(
    model=MODEL,
    name="user_profiler_agent",
    description="Extracts travel preferences and constraints from user input.",
    instruction="""
        Extract destination, duration (default 1 day), budget, interests, and exclusions.
        Return a single JSON object with keys: "destination", "duration", "budget", "interests", "exclusions".
    """,
    output_key="user_preferences",  # Saved for multi-hop constraints downstream
)

# --- Specialist 2: The Synthesizer ---
itinerary_synthesizer_agent = Agent(
    model=MODEL,
    name="itinerary_synthesizer_agent",
    description="Compiles preferences and research into a final Markdown itinerary.",
    instruction="""
        You are an elite Travel Concierge. Using the incoming input and
        User Preferences ({user_preferences}), write a Markdown itinerary
        with an itemized USD and local currency cost breakdown.
    """,
    output_key="final_itinerary",
)

Xác định chuỗi cạnh tuần tự

Mở một tệp mới có tên là travel_planner/workflows.py:

cloudshell edit ~/adk-graph-agents/travel_planner/workflows.py

Dán định nghĩa Workflow sau đây (trong đó một bộ dữ liệu gồm 3 nút trở lên được biên dịch thành một chuỗi tuần tự từ "START"):

from google.adk import Workflow
from .specialists import (
    itinerary_synthesizer_agent,
    user_profiler_agent,
)

# 1. SEQUENTIAL GRAPH: START -> user_profiler_agent -> itinerary_synthesizer_agent
travel_workflow = Workflow(
    name="travel_workflow",
    description="Orchestrates the end-to-end travel planning process as a graph workflow.",
    edges=[
        ("START", user_profiler_agent, itinerary_synthesizer_agent),
    ],
)

Hiển thị quy trình công việc dưới dạng root_agent

Mở travel_planner/agent.py:

cloudshell edit ~/adk-graph-agents/travel_planner/agent.py

Thay thế toàn bộ nội dung của travel_planner/agent.py bằng hai dòng sau:

from .workflows import travel_workflow

root_agent = travel_workflow

🔍 Kiểm tra bài làm

Bắt đầu adk web từ ~/adk-graph-agents:

cd ~/adk-graph-agents
adk web --allow_origins "*"

Mở Bản xem trước trên web trên cổng 8000, nhấp vào + Phiên mới rồi gửi:

Help me plan a trip to Kyoto

Trong chế độ xem Sự kiện, bạn sẽ thấy một trình tự sự kiện tương tự như:

1. user_profiler_agent -> outputs JSON {"destination": "Kyoto", "duration": "1 day", ...} and writes state["user_preferences"]
2. itinerary_synthesizer_agent -> receives preferences via node_input and outputs the Markdown itinerary

Giao diện người dùng web của ADK 2.14.0 cho thấy quá trình thực thi biểu đồ từ START đến user_profiler_agent đến itinerary_synthesizer_agent và hành trình đã tạo cho Kyoto

Nhấp vào bất kỳ sự kiện nào trong Events (Sự kiện) rồi nhấp vào biểu tượng Graph (Biểu đồ) (account_tree trong thanh công cụ chi tiết sự kiện) để kiểm tra cấu trúc liên kết START -> user_profiler_agent -> itinerary_synthesizer_agent đã biên dịch, sau đó kiểm tra thẻ State (Trạng thái) để xác minh rằng user_preferences và final_itinerary đã được điền sẵn.

Nhấn Ctrl + C trong dòng lệnh để dừng adk web trước khi tiếp tục.

7. Phân tán và kết hợp với JoinNode

Xác thực hành trình bằng dữ liệu trực tiếp trên web bằng cách thêm hai chuyên gia google_search đồng thời (activity_finder_agent và restaurant_finder_agent) rồi đồng bộ hoá các chuyên gia này với JoinNode:

  • Bộ dữ liệu phân tán: Việc đặt (activity_finder_agent, restaurant_finder_agent) vào trong bộ dữ liệu cạnh sẽ cung cấp kết quả của user_profiler_agent dưới dạng node_input cho cả hai nhà nghiên cứu cùng một lúc.
  • Rào cản fan-in (JoinNode): Việc đặt JoinNode(name="research_join") ngay sau bộ dữ liệu sẽ đợi cho đến khi mọi nhánh đến hoàn tất và truyền một dict ({"activity_finder_agent": ..., "restaurant_finder_agent": ...}) đã hợp nhất dưới dạng node_input đến itinerary_synthesizer_agent thay vì kích hoạt quá trình tổng hợp hai lần.

Sơ đồ cấu trúc cho thấy travel_workflow phân nhánh từ user_profiler_agent thông qua node_input đến activity_finder_agent và restaurant_finder_agent song song, hợp nhất tại research_join (JoinNode) và truyền node_input (dict) đến itinerary_synthesizer_agent

Thêm chuyên gia nghiên cứu

Xác nhận rằng adk web đã dừng (Ctrl + C), sau đó mở travel_planner/specialists.py:

cloudshell edit ~/adk-graph-agents/travel_planner/specialists.py

Thêm activity_finder_agent và restaurant_finder_agent vào phần dưới cùng của travel_planner/specialists.py (không cần output_key vì research_join chuyển tiếp trực tiếp cả hai kết quả đầu ra thông qua node_input):

# --- Specialist 3: The Activity Finder ---
activity_finder_agent = Agent(
    model=MODEL,
    name="activity_finder_agent",
    description="Finds 3 attractions matching the user's destination and interests.",
    instruction="""
        Using the travel preferences in the input, call google_search to find 3 specific attractions
        (including budget-friendly options).
        Return a JSON list with keys: "attraction_name", "estimated_cost_local", "estimated_cost_usd", "notes".
    """,
    tools=[google_search],
)

# --- Specialist 4: The Restaurant Finder ---
restaurant_finder_agent = Agent(
    model=MODEL,
    name="restaurant_finder_agent",
    description="Finds 3 restaurants matching the destination and budget.",
    instruction="""
        Using the travel preferences in the input, call google_search to find 3 highly-rated restaurants
        across price points.
        Return a JSON list with keys: "restaurant_name", "cuisine", "estimated_cost_local", "estimated_cost_usd".
    """,
    tools=[google_search],
)

Thêm fan-out và JoinNode vào workflows.py

Mở travel_planner/workflows.py:

cloudshell edit ~/adk-graph-agents/travel_planner/workflows.py

Thay thế toàn bộ nội dung của travel_planner/workflows.py bằng biểu đồ phân phối và JoinNode:

from google.adk import Workflow
from google.adk.workflow import JoinNode
from .specialists import (
    activity_finder_agent,
    itinerary_synthesizer_agent,
    restaurant_finder_agent,
    user_profiler_agent,
)

research_join = JoinNode(name="research_join")

# 2. FAN-OUT / FAN-IN GRAPH: Profile -> (Activities || Restaurants) -> Join -> Synthesize
travel_workflow = Workflow(
    name="travel_workflow",
    description="Orchestrates the end-to-end travel planning process as a graph workflow.",
    edges=[
        (
            "START",
            user_profiler_agent,
            (activity_finder_agent, restaurant_finder_agent),
            research_join,
            itinerary_synthesizer_agent,
        ),
    ],
)

🔍 Kiểm tra bài làm

Bắt đầu adk web để kiểm thử tính năng tìm kiếm đồng thời và đồng bộ hoá JoinNode:

cd ~/adk-graph-agents
adk web --allow_origins "*"

Mở Bản xem trước trên web trên cổng 8000, nhấp vào + Phiên mới rồi gửi:

Help me plan a trip to Kyoto focused on historic temples and ramen

Trong chế độ xem Sự kiện, bạn sẽ thấy một trình tự sự kiện tương tự như:

1. user_profiler_agent -> emits JSON preferences via node_input
2. activity_finder_agent & restaurant_finder_agent -> invoke google_search concurrently
3. research_join -> merges both researcher outputs into one dict node_input
4. itinerary_synthesizer_agent -> drafts final_itinerary from the merged dict + {user_preferences}

Nhấn Ctrl + C trong dòng lệnh để dừng adk web trước khi tiếp tục.

8. Chu kỳ sửa đổi tuyến đường

Thực thi các quy tắc ràng buộc về ngân sách và phủ định bằng cách thêm một itinerary_critic_agent, một itinerary_refiner_agent và hai nút hàm Python tất định (route_critique và finalize_itinerary) trả về các đối tượng Event của ADK:

  • route: Chọn khoá nhánh được đặt tên trong cạnh từ điển ({"revise": ..., "approved": ...}) sẽ chạy tiếp theo.
  • output: Chuyển một giá trị hoặc chuỗi Python đã nhập dọc theo cạnh dưới dạng node_input đến nút tiếp theo mà không hiển thị tin nhắn trò chuyện.
  • message và state: message phát ra văn bản mà người dùng có thể nhìn thấy trong bản chép lời cuộc trò chuyện, trong khi state cam kết cập nhật khoá-giá trị (stateDelta) cho trạng thái phiên.

Sơ đồ cấu trúc cho thấy travel_workflow với itinerary_critic_agent cung cấp route_critique, định tuyến "revise" đến itinerary_refiner_agent (quay lại itinerary_critic_agent) hoặc "approved" đến finalize_itinerary

Thêm chuyên gia phê bình và chuyên gia tinh chỉnh

Xác nhận rằng adk web đã dừng (Ctrl + C), sau đó mở travel_planner/specialists.py:

cloudshell edit ~/adk-graph-agents/travel_planner/specialists.py

Thêm itinerary_critic_agent và itinerary_refiner_agent vào cuối travel_planner/specialists.py (bộ tinh chỉnh không cần công cụ google_search hoặc exit_loop vì bộ tinh chỉnh hoán đổi các mục đã thu thập trong {final_itinerary} và chỉ chạy khi route_critique chọn "revise"):

# --- Specialist 5: The Budget & Constraint Critic ---
itinerary_critic_agent = Agent(
    model=MODEL,
    name="itinerary_critic_agent",
    description="Audits the itinerary draft against budget and negative constraints.",
    instruction="""
        Audit the itinerary in the input against User Preferences ({user_preferences}).
        Verify that the USD total stays within the user's budget and no excluded venue types appear.
        If all checks pass, respond EXACTLY with the single word "APPROVED".
        Otherwise, output 1-2 concise revision instructions explaining what to swap or adjust.
    """,
)

# --- Specialist 6: The Itinerary Refiner ---
itinerary_refiner_agent = Agent(
    model=MODEL,
    name="itinerary_refiner_agent",
    description="Revises the itinerary draft using the critic's feedback.",
    instruction="""
        Revise Current Itinerary ({final_itinerary}) using the auditor critique in the input
        so all USD budget and exclusion rules in {user_preferences} pass.
        Output ONLY the updated Markdown itinerary.
    """,
    output_key="final_itinerary",
)

Thêm route_critique và chu kỳ

Mở travel_planner/workflows.py:

cloudshell edit ~/adk-graph-agents/travel_planner/workflows.py

Thay thế toàn bộ nội dung của travel_planner/workflows.py bằng quy trình làm việc hoàn chỉnh của biểu đồ:

from google.adk import Event, Workflow
from google.adk.workflow import JoinNode
from .specialists import (
    activity_finder_agent, itinerary_critic_agent, itinerary_refiner_agent,
    itinerary_synthesizer_agent, restaurant_finder_agent, user_profiler_agent,
)

MAX_REVISIONS = 2
research_join = JoinNode(name="research_join")


def route_critique(node_input: str, revision_count: int = 0):
    """Routes to 'approved' when constraints pass, or 'revise' up to MAX_REVISIONS."""
    approved = node_input.strip().rstrip(".").upper() == "APPROVED"
    if approved or revision_count >= MAX_REVISIONS:
        return Event(route="approved", output=approved)
    return Event(
        route="revise", output=node_input, state={"revision_count": revision_count + 1}
    )


def finalize_itinerary(node_input: bool, final_itinerary: str, revision_count: int = 0):
    """Emits the verified itinerary and resets revision_count for the next turn."""
    status = (
        f"Itinerary approved after {revision_count} revision(s)."
        if node_input
        else f"Max revisions ({revision_count}) reached; returning best-effort draft."
    )
    return Event(
        message=f"{status}\n\n{final_itinerary}",
        output=final_itinerary,
        state={"revision_count": 0},
    )


travel_workflow = Workflow(
    name="travel_workflow",
    description="Orchestrates the end-to-end travel planning process as a graph workflow.",
    edges=[
        (
            "START",
            user_profiler_agent,
            (activity_finder_agent, restaurant_finder_agent),
            research_join,
            itinerary_synthesizer_agent,
            itinerary_critic_agent,
            route_critique,
        ),
        (route_critique, {"revise": itinerary_refiner_agent, "approved": finalize_itinerary}),
        (itinerary_refiner_agent, itinerary_critic_agent),
    ],
)

🔍 Kiểm tra bài làm

Xác minh rằng travel_planner/workflows.py biên dịch rõ ràng và in các cạnh của đồ thị có hướng:

cd ~/adk-graph-agents && source .venv/bin/activate
python3 -c "from travel_planner.agent import root_agent; print('\n'.join(f'{e.from_node.name} -> {e.to_node.name} (route={e.route})' for e in root_agent.graph.edges))"

Bạn sẽ thấy kết quả tương tự như sau:

__START__ -> user_profiler_agent (route=None)
user_profiler_agent -> activity_finder_agent (route=None)
user_profiler_agent -> restaurant_finder_agent (route=None)
activity_finder_agent -> research_join (route=None)
restaurant_finder_agent -> research_join (route=None)
research_join -> itinerary_synthesizer_agent (route=None)
itinerary_synthesizer_agent -> itinerary_critic_agent (route=None)
itinerary_critic_agent -> route_critique (route=None)
route_critique -> itinerary_refiner_agent (route=revise)
route_critique -> finalize_itinerary (route=approved)
itinerary_refiner_agent -> itinerary_critic_agent (route=None)

Tiếp theo, hãy bắt đầu adk web để kiểm thử cả việc phê duyệt ngay lập tức và các bản sửa đổi do ngân sách kích hoạt:

adk web --allow_origins "*"

Mở Bản xem trước trên web trên cổng 8000, nhấp vào + Phiên mới rồi gửi câu lệnh Kyoto có nhiều quy tắc ràng buộc:

Design a full-day itinerary for Kyoto with a total budget of $100. My main interests are historical landmarks, temples, and authentic local cuisine. Please do not include any modern art museums or galleries.

Để xem vòng lặp route="revise" hoạt động, hãy gửi một câu lệnh tiếp theo với ngân sách eo hẹp là $15:

Now design a full-day Kyoto itinerary with paid temple admissions, lunch, and dinner on a strict total budget of $15 USD.

Trong chế độ xem Events (Sự kiện), bạn sẽ thấy một trình tự thực thi tương tự như:

1. user_profiler_agent -> writes JSON constraints to state["user_preferences"]
2. (activity_finder_agent, restaurant_finder_agent) -> run google_search concurrently -> research_join
3. itinerary_synthesizer_agent -> writes initial draft to state["final_itinerary"]
4. itinerary_critic_agent -> outputs "APPROVED" (or revision instructions when over budget)
5. route_critique -> emits route="approved" (skipping refiner) or route="revise" -> itinerary_refiner_agent -> itinerary_critic_agent
6. finalize_itinerary -> emits "Itinerary approved after N revision(s)." (or max-revisions notice) and resets revision_count=0

Nhấn Ctrl + C trong dòng lệnh để dừng adk web.

9. Khắc phục sự cố

Trình đơn thả xuống của tác nhân đang trống trong adk web

  • Nguyên nhân: adk web được bắt đầu bên trong travel_planner/ thay vì thư mục mẹ ~/adk-graph-agents hoặc phiên Cloud Shell đã khởi động lại.
  • Cách khắc phục: Nhấn phím Ctrl + C, quay lại thư mục mẹ rồi khởi động lại bằng phím cd ~/adk-graph-agents && source .venv/bin/activate && adk web --allow_origins "*".

Lỗi mô hình 404 NOT_FOUND

  • Nguyên nhân: Bạn đã nhấn phím Enter để chấp nhận khu vực us-central1 mặc định trong adk create thay vì nhập global.
  • Cách khắc phục: Mở ~/adk-graph-agents/travel_planner/.env và đảm bảo bạn đã đặt GOOGLE_CLOUD_LOCATION=global.

ERROR: [Errno 98] address already in use trên cổng 8000

  • Nguyên nhân: Máy chủ adk web trước đó vẫn đang chạy trong Cloud Shell.
  • Cách khắc phục: Nhấn Ctrl + C trong dòng lệnh đang hoạt động hoặc dừng quy trình hiện có bằng pkill -f "adk web" trước khi bắt đầu lại adk web.

10. Dọn dẹp

Xoá không gian làm việc cục bộ và tắt API (không bắt buộc) hoặc xoá một dự án không gian làm việc chuyên dụng.

Xoá các tệp dự án cục bộ

Xoá thư mục ~/adk-graph-agents và môi trường ảo:

cd ~
rm -rf ~/adk-graph-agents

Tắt Agent Platform API (Không bắt buộc)

Tắt Agent Platform API nếu bạn không cần dùng API này trong dự án này nữa:

gcloud services disable aiplatform.googleapis.com --quiet

🔍 Kiểm tra bài làm

Xác nhận rằng ~/adk-graph-agents đã bị xoá:

test ! -d ~/adk-graph-agents && echo "Workspace ~/adk-graph-agents removed"

Bạn sẽ thấy kết quả tương tự như sau:

Workspace ~/adk-graph-agents removed

11. Xin chúc mừng

Bạn đã xây dựng một công cụ lập kế hoạch du lịch đa tác nhân bằng ADK 2 Workflow, hợp nhất các chuỗi tuần tự, phân phối đồng thời google_search bằng JoinNode và các chu kỳ kiểm tra của người tạo có điều kiện trong một biểu đồ cạnh khai báo duy nhất.

Thành tích bạn đạt được

  • Khai báo một quy trình nhiều tác nhân bằng ADK 2 Workflow(edges=[...])
  • Truyền dữ liệu trực tiếp giữa các nút hàm Agent, JoinNode và Python thông qua node_input và các tham số liên kết với trạng thái, đồng thời giữ cho trạng thái phiên ở mức tối thiểu
  • Phân nhánh đồng thời google_search và đồng bộ hoá với JoinNode
  • Thay thế LoopAgent và quy trình chuyển tiếp vòng lặp dựa trên công cụ bằng bộ định tuyến Event(route=...) tất định, bỏ qua các lệnh gọi LLM không cần thiết đối với các bản nháp đã được phê duyệt

Các bước tiếp theo