1. The Mission

Bạn đang trôi dạt trong không gian bao la, im lặng và chưa được khám phá. Một Xung năng lượng mặt trời khổng lồ đã xé toạc con tàu của bạn qua một vết nứt không gian, khiến bạn mắc kẹt trong một vùng vũ trụ không có trong bất kỳ biểu đồ sao nào.
Sau nhiều ngày sửa chữa vất vả, tiếng động cơ quen thuộc cuối cùng cũng trở lại. Tàu vũ trụ của bạn đang hoạt động. Bạn thậm chí còn thiết lập được đường truyền tải lên tầm xa đến Tàu mẹ. Chuyến bay sắp khởi hành. Bạn đã sẵn sàng về nhà.
Nhưng khi bạn chuẩn bị kích hoạt ổ đĩa nhảy, một tín hiệu cấp cứu đã cắt ngang tín hiệu nhiễu. Các cảm biến của bạn xác định chính xác một lời kêu cứu từ hành tinh được chỉ định là "Ozymandias". Những người sống sót bị mắc kẹt trên thế giới đang chết dần này, con tàu của họ bị mắc cạn. Nhiệm vụ của bạn rất quan trọng: giải cứu họ trước khi bầu khí quyển của hành tinh sụp đổ.
Phương tiện duy nhất để họ trốn thoát là một chiếc tên lửa cổ xưa, bỏ hoang được chế tạo bằng Công nghệ ngoài hành tinh. Mặc dù vẫn hoạt động được, nhưng Động cơ Warp của chiếc tên lửa này đã bị hỏng. Để cứu những người sống sót, bạn phải kết nối từ xa với Volatile Workbench của họ và tự lắp ráp một ổ đĩa thay thế.
Thử thách
Bạn chưa từng tiếp xúc với công nghệ ngoài hành tinh này, mà công nghệ này lại nổi tiếng là rất dễ hỏng. Một thành phần không ổn định có thể trở thành mối nguy hiểm phóng xạ trong vài giây. Bạn có một lần thử để vận hành Volatile Workbench. Trợ lý AI hiện tại của bạn đang gặp khó khăn trong việc xử lý đồng thời dữ liệu trực quan và sách hướng dẫn kỹ thuật, dẫn đến việc đưa ra hướng dẫn ảo tưởng và bỏ lỡ cảnh báo nguy hiểm.
Để thành công, bạn phải nâng cấp AI từ một thực thể nguyên khối thành một Hệ thống đa tác nhân có khả năng cộng tác.
Mục tiêu của nhiệm vụ:
Lắp ráp Warp Drive bằng cách làm theo hướng dẫn chuyên biệt theo thời gian thực từ hệ thống đa tác nhân mới của bạn.

Sản phẩm bạn sẽ tạo ra

- Một hệ thống AI đa tác nhân hai chiều theo thời gian thực, có Tác nhân điều phối trung tâm quản lý lượt tương tác của người dùng và phối hợp với các tác nhân chuyên trách.
- Một Architect Agent kết nối với cơ sở dữ liệu Redis để truy xuất và phân phát dữ liệu sơ đồ.
- Màn hình an toàn chủ động sử dụng các công cụ truyền phát trực tiếp để phân tích nguồn cấp dữ liệu video trực tiếp nhằm phát hiện các mối nguy hiểm về thị giác và kích hoạt cảnh báo theo thời gian thực.
- Một giao diện người dùng dựa trên React cung cấp giao diện người dùng để tương tác với hệ thống, truyền phát trực tuyến video và âm thanh đến các tác nhân phụ trợ.
Kiến thức bạn sẽ học được
Công nghệ / Khái niệm | Mô tả |
Bộ công cụ phát triển (ADK) của Google Agent | Bạn sẽ sử dụng ADK để tạo, kiểm thử và quản lý các tác nhân, tận dụng khung của ADK để xử lý thông tin liên lạc theo thời gian thực, tích hợp công cụ và vòng đời của tác nhân. |
Truyền trực tuyến hai chiều (Bidi) | Bạn sẽ triển khai một tác nhân truyền trực tuyến hai chiều cho phép giao tiếp tự nhiên, có độ trễ thấp và hai chiều, cho phép cả người dùng và AI ngắt lời và phản hồi theo thời gian thực. |
Hệ thống đa tác nhân | Bạn sẽ tìm hiểu cách thiết kế một hệ thống AI phân tán, trong đó một tác nhân chính sẽ uỷ quyền các tác vụ cho các tác nhân chuyên biệt, cho phép tách biệt các mối lo ngại và có kiến trúc có khả năng mở rộng hơn. |
Giao thức Agent-to-Agent (A2A) | Bạn sẽ sử dụng giao thức A2A để cho phép Dispatch Agent và Architect Agent giao tiếp với nhau, nhờ đó chúng có thể khám phá các chức năng của nhau và trao đổi dữ liệu. |
Công cụ phát trực tiếp | Bạn sẽ triển khai một công cụ truyền phát hoạt động như một quy trình nền, liên tục phân tích nguồn cấp dữ liệu video để theo dõi các thay đổi về trạng thái (mối nguy hiểm) và chủ động đưa ra kết quả. |
Google Cloud Run và Memorystore | Bạn sẽ triển khai toàn bộ ứng dụng nhiều tác nhân vào một môi trường sản xuất, sử dụng Cloud Run để lưu trữ các dịch vụ tác nhân và Memorystore (Redis) làm cơ sở dữ liệu cố định. |
FastAPI và WebSockets | Phần phụ trợ được xây dựng bằng FastAPI và WebSockets để xử lý hoạt động giao tiếp theo thời gian thực, hiệu suất cao cần thiết cho việc truyền trực tuyến âm thanh, video và phản hồi của trợ lý ảo. |
Giao diện người dùng React | Bạn sẽ làm việc với một giao diện người dùng dựa trên React, có chức năng ghi lại và truyền phát nội dung nghe nhìn của người dùng (âm thanh/video) cũng như hiển thị các phản hồi theo thời gian thực từ các tác nhân AI. |
2. Thiết lập môi trường
Truy cập Cloud Shell
👉Nhấp vào biểu tượng Kích hoạt Cloud Shell ở đầu bảng điều khiển Cloud (Đây là biểu tượng có hình dạng thiết bị đầu cuối ở đầu ngăn Cloud Shell), 
👉Nhấp vào nút "Mở trình chỉnh sửa" (nút này trông giống như một thư mục đang mở có bút chì). Thao tác này sẽ mở Trình chỉnh sửa mã Cloud Shell trong cửa sổ. Bạn sẽ thấy một trình khám phá tệp ở bên trái. 
👉Mở cửa sổ dòng lệnh trong IDE trên đám mây,

👉💻 Trong thiết bị đầu cuối, hãy xác minh rằng bạn đã được xác thực và dự án được đặt thành mã dự án của bạn bằng lệnh sau:
gcloud auth list
Bạn sẽ thấy tài khoản của mình được liệt kê là (ACTIVE).
Điều kiện tiên quyết
ℹ️ Cấp 0 là không bắt buộc (nhưng nên dùng)
Bạn có thể hoàn thành nhiệm vụ này mà không cần đạt Cấp 0, nhưng hoàn thành nhiệm vụ này trước sẽ mang đến trải nghiệm sống động hơn, cho phép bạn thấy đèn hiệu của mình sáng lên trên bản đồ toàn cầu khi bạn tiến bộ.
Thiết lập môi trường dự án
Quay lại thiết bị đầu cuối, hoàn tất cấu hình bằng cách đặt dự án đang hoạt động và bật các dịch vụ cần thiết của Google Cloud (Cloud Run, Vertex AI, v.v.).
👉💻 Trong thiết bị đầu cuối, hãy đặt mã dự án:
gcloud config set project $(cat ~/project_id.txt) --quiet
👉💻 Bật các dịch vụ bắt buộc:
gcloud services enable compute.googleapis.com \
artifactregistry.googleapis.com \
run.googleapis.com \
cloudbuild.googleapis.com \
iam.googleapis.com \
aiplatform.googleapis.com \
cloudresourcemanager.googleapis.com \
redis.googleapis.com \
vpcaccess.googleapis.com
Cài đặt các phần phụ thuộc
👉💻 Chuyển đến Cấp độ 4 và cài đặt các gói Python bắt buộc:
cd $HOME/way-back-home/level_4
uv sync
Sau đây là các phần phụ thuộc chính:
Gói | Mục đích |
| Khung web hiệu suất cao cho Trạm vệ tinh và tính năng truyền trực tuyến SSE |
| Cần có máy chủ ASGI để chạy ứng dụng FastAPI |
| Bộ công cụ phát triển tác nhân được dùng để tạo Tác nhân hình thành |
| Thư viện giao thức Agent-to-Agent để giao tiếp chuẩn hoá |
| Ứng dụng gốc để truy cập vào các mô hình Gemini |
| Ứng dụng Python để kết nối với Schematic Vault (Memorystore) |
| Hỗ trợ giao tiếp hai chiều theo thời gian thực |
| Quản lý các biến môi trường và bí mật cấu hình |
| Xác thực dữ liệu và quản lý chế độ cài đặt |
Xác minh chế độ thiết lập
Trước khi bắt đầu viết mã, hãy đảm bảo rằng tất cả các hệ thống đều hoạt động bình thường. Chạy tập lệnh xác minh để kiểm tra Dự án Google Cloud, API và các phần phụ thuộc Python.
👉💻 Chạy tập lệnh xác minh:
cd $HOME/way-back-home/level_4/scripts
chmod +x verify_setup.sh
. verify_setup.sh
👀 Bạn sẽ thấy một loạt Dấu kiểm màu xanh lục (✅).
- Nếu bạn thấy Dấu chéo màu đỏ (❌), hãy làm theo các lệnh khắc phục được đề xuất trong đầu ra (ví dụ:
gcloud services enable ...hoặcpip install ...). - Lưu ý: Cảnh báo màu vàng cho
.envhiện có thể chấp nhận được; chúng ta sẽ tạo tệp đó ở bước tiếp theo.
🚀 Verifying Mission Bravo (Level 4) Infrastructure... ✅ Google Cloud Project: xxxxxxx ✅ Cloud APIs: Active ✅ Python Environment: Ready 🎉 SYSTEMS ONLINE. READY FOR MISSION.
3. Xây dựng kho lưu trữ sơ đồ trong Redis và BiDirecitional Agent bằng ADK
Bạn đã tìm thấy kho lưu trữ sơ đồ hành tinh chứa bản thiết kế cho tên lửa bỏ hoang. Để truy xuất dữ liệu này một cách chính xác, bạn phải tương tác với giao diện quản lý chuyên dụng của kho lưu trữ: tác nhân Architect.

Cung cấp Vault theo sơ đồ (Redis)
Trước khi Kiến trúc sư có thể hỗ trợ chúng tôi, chúng tôi phải đảm bảo dữ liệu được lưu trữ trong một môi trường an toàn, có tính sẵn sàng cao. Chúng ta sẽ dùng Redis làm kho dữ liệu nhanh cho sơ đồ của người ngoài hành tinh. Để thuận tiện cho việc phát triển, chúng ta sẽ tạo một phiên bản Redis cục bộ, nhưng hướng dẫn về cách triển khai vào môi trường sản xuất bằng Google Cloud Memorystore sẽ được cung cấp sau.
👉💻 Chạy các lệnh sau trong thiết bị đầu cuối để cung cấp phiên bản Redis (Quá trình này có thể mất từ 2 đến 3 phút):
docker run -d --name ozymandias-vault -p 6379:6379 redis:8.6-rc1-alpine
👉💻 Để tải dữ liệu sơ bộ, hãy chạy lệnh sau để nhập Redis Shell:
docker exec -it ozymandias-vault redis-cli
(Câu lệnh của bạn sẽ thay đổi thành 127.0.0.1:6379)
👉💻 Dán các lệnh này vào bên trong:
RPUSH "HYPERION-X" "Warp Core" "Flux Pipe" "Ion Thruster"
RPUSH "NOVA-V" "Ion Thruster" "Warp Core" "Flux Pipe"
RPUSH "OMEGA-9" "Flux Pipe" "Ion Thruster" "Warp Core"
RPUSH "GEMINI-MK1" "Coolant Tank" "Servo" "Fuel Cell"
RPUSH "APOLLO-13" "Warp Core" "Coolant Tank" "Ion Thruster"
RPUSH "VORTEX-7" "Quantum Cell" "Graviton Coil" "Plasma Injector"
RPUSH "CHRONOS-ALPHA" "Shield Emitter" "Data Crystal" "Quantum Cell"
RPUSH "NEBULA-Z" "Plasma Injector" "Flux Pipe" "Graviton Coil"
RPUSH "PULSAR-B" "Data Crystal" "Servo" "Shield Emitter"
RPUSH "TITAN-PRIME" "Ion Thruster" "Quantum Cell" "Warp Core"
👉💻 Nhập exit để quay lại shell bình thường.
👉💻 Để kiểm tra xem dữ liệu có tồn tại hay không bằng cách truy vấn một tàu cụ thể ngay trên thiết bị đầu cuối, hãy chạy:
# Check 'TITAN-PRIME'
docker exec ozymandias-vault redis-cli LRANGE "TITAN-PRIME" 0 -1
👀 Đây là kết quả đầu ra dự kiến:
1) "Ion Thruster" 2) "Quantum Cell" 3) "Warp Core"
Triển khai Architect Agent
Architect Agent là một tác nhân chuyên biệt chịu trách nhiệm truy xuất bản thiết kế sơ đồ từ kho lưu trữ Redis của chúng tôi. Nó đóng vai trò là một giao diện dữ liệu chuyên dụng, đảm bảo Dispatch Agent chính nhận được thông tin chính xác và có cấu trúc mà không cần biết logic cơ sở dữ liệu cơ bản.

Google Agent Development Kit (ADK) là khung mô-đun giúp thiết lập nhiều tác nhân này. Nó xử lý 2 lớp quan trọng:
- Vòng đời kết nối và phiên: Việc tương tác với các API theo thời gian thực đòi hỏi phải quản lý giao thức phức tạp – xử lý các lượt bắt tay, xác thực và tín hiệu duy trì kết nối.
- Gọi hàm: Đây là "Hành trình khứ hồi giữa Mô hình – Mã – Mô hình". Khi LLM quyết định rằng cần dữ liệu, LLM sẽ xuất một lệnh gọi hàm có cấu trúc. ADK sẽ chặn lệnh này, thực thi mã Python của bạn (
lookup_schematic_tool) và đưa kết quả trở lại ngữ cảnh của mô hình trong vài mili giây.
Giờ đây, chúng ta sẽ xây dựng Architect. Nhân viên hỗ trợ này không có quyền truy cập vào camera. Hàm này chỉ dùng để nhận "Tên ổ đĩa" và trả về "Danh sách linh kiện" từ cơ sở dữ liệu.
👉💻 Chúng ta sẽ dùng lệnh adk create. Đây là một công cụ trong Agent Development Kit (ADK) (Bộ công cụ phát triển tác nhân) giúp tự động tạo mã nguyên mẫu và cấu trúc tệp cho một tác nhân mới, giúp chúng ta tiết kiệm thời gian thiết lập.
cd $HOME/way-back-home/level_4/backend/
uv run adk create architect_agent
Định cấu hình tác nhân
CLI sẽ khởi chạy một trình hướng dẫn thiết lập tương tác. Hãy sử dụng các phản hồi sau để định cấu hình tác nhân:
- Chọn mô hình: Chọn Lựa chọn 1 (Gemini Flash).
- Lưu ý: Phiên bản cụ thể (ví dụ: 2.5, 3.0) có thể thay đổi tuỳ vào số lượng bất động sản còn trống. Luôn chọn biến thể "Flash" để có tốc độ nhanh.
- Chọn một phần phụ trợ: Chọn Tuỳ chọn 2 (Vertex AI).
- Nhập mã dự án trên Google Cloud: Nhấn Enter để chấp nhận giá trị mặc định (được phát hiện từ môi trường của bạn).
- Nhập khu vực của Google Cloud: Nhấn Enter để chấp nhận giá trị mặc định (
us-central1).
👀 Tương tác của bạn với thiết bị đầu cuối sẽ có dạng như sau:
(way-back-home) user@cloudshell:~/way-back-home/level_4/agent$ adk create architect_agent Choose a model for the root agent: 1. gemini-2.5-flash 2. Other models (fill later) Choose model (1, 2): 1 1. Google AI 2. Vertex AI Choose a backend (1, 2): 2 You need an existing Google Cloud account and project... Enter Google Cloud project ID [your-project-id]: <PRESS ENTER> Enter Google Cloud region [us-central1]: <PRESS ENTER> Agent created in /home/user/way-back-home/level_4/agent/architect_agent: - .env - __init__.py - agent.py
Lúc này, bạn sẽ thấy thông báo thành công Agent created. Thao tác này sẽ tạo mã khung mà chúng ta sẽ sửa đổi trong bước tiếp theo.
👉✏️ Chuyển đến và mở tệp $HOME/way-back-home/level_4/backend/architect_agent/agent.py mới tạo trong trình chỉnh sửa. Thêm đoạn mã công cụ vào tệp sau dòng nhập đầu tiên:
import os
import redis
REDIS_IP = os.environ.get('REDIS_HOST', 'localhost')
r = redis.Redis(host=REDIS_IP, port=6379, decode_responses=True)
def lookup_schematic_tool(drive_name: str) -> list[str]:
"""Returns the ordered list of parts for a drive from local Redis."""
# Logic to clean input like "TARGET: X" -> "X"
clean_name = drive_name.replace("TARGET:", "").replace("TARGET", "").strip()
clean_name = clean_name.replace(":", "").strip()
# LRANGE gets all items in the list (index 0 to -1)
result = r.lrange(clean_name, 0, -1)
if not result:
print(f"[ARCHITECT] Error: Drive ID '{clean_name}' not found in Redis.")
return ["ERROR: Drive ID not found."]
print(f"[ARCHITECT] Returning schematic for {clean_name}: {result}")
return result
👉✏️ Thay thế toàn bộ dòng instruction trong định nghĩa root_agent bằng dòng sau, đồng thời thêm công cụ mà chúng ta đã xác định trước đó:
instruction='''SYSTEM ROLE: Database API.
INPUT: Text string (Drive Name).
TASK: Run `lookup_schematic_tool`.
OUTPUT: Return ONLY the raw list from the tool.
CONSTRAINT: Do NOT add conversational text.
''',
tools=[lookup_schematic_tool],
Ưu điểm của ADK
Với Architect trực tuyến, giờ đây chúng tôi đã có một nguồn thông tin xác thực. Trước khi kết nối với tác nhân chính,Bộ công cụ phát triển tác nhân (ADK) mang lại một lợi thế đáng kể bằng cách đơn giản hoá sự phức tạp của việc xây dựng và kiểm thử các tác nhân AI. Với bảng điều khiển dành cho nhà phát triển adk web tích hợp sẵn, chúng ta có thể tách biệt và xác minh chức năng của Architect Agent, cụ thể là khả năng gọi công cụ của Architect Agent, trước khi tích hợp Architect Agent vào hệ thống đa tác nhân lớn hơn. Phương pháp phát triển và kiểm thử theo mô-đun này là yếu tố quan trọng để xây dựng các ứng dụng AI mạnh mẽ và đáng tin cậy.
👉💻 Trong cửa sổ dòng lệnh, hãy chạy:
cd $HOME/way-back-home/level_4/
. scripts/check_redis.sh
cd $HOME/way-back-home/level_4/backend/
uv run adk web
👀 Chờ đến khi bạn thấy:
+-----------------------------------------------------------------------------+ | ADK Web Server started | | | | For local testing, access at http://127.0.0.1:8000. | +-----------------------------------------------------------------------------+ INFO: Application startup complete. INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
- Nhấp vào biểu tượng Xem trước trên web trong thanh công cụ Cloud Shell. Chọn Thay đổi cổng, đặt thành 8000 rồi nhấp vào Thay đổi và xem trước.

- Chọn architect_agent.
- Kích hoạt công cụ: Trong giao diện trò chuyện, hãy nhập:
CHRONOS-ALPHA(hoặc bất kỳ mã nhận dạng nào của Drive trong cơ sở dữ liệu sơ đồ). - Quan sát hành vi:
- Kiến trúc sư phải kích hoạt ngay
lookup_schematic_tool. - Do hướng dẫn nghiêm ngặt của hệ thống, nên hệ thống chỉ trả về danh sách các phần (ví dụ:
['Shield Emitter', 'Data Crystal', 'Quantum Cell']) mà không có từ đệm trong cuộc trò chuyện.
- Kiến trúc sư phải kích hoạt ngay
- Xác minh nhật ký: Xem cửa sổ dòng lệnh. Bạn sẽ thấy nhật ký thực thi thành công:
[ARCHITECT] Returning schematic for CHRONOS-ALPHA: ['Shield Emitter', 'Data Crystal', 'Quantum Cell']!(architect_agent adk)[img/03-02-adkweb.png]
Nếu bạn thấy nhật ký thực thi công cụ và phản hồi dữ liệu chất lượng cao, thì tức là tác nhân chuyên trách đang hoạt động như dự kiến. Thư viện này có thể xử lý các yêu cầu, truy vấn kho lưu trữ và trả về dữ liệu có cấu trúc.
👉💻 Nhấn Ctrl+C để thoát.
Khởi chạy Máy chủ A2A
Để kết nối Dispatch Agent với Architect, chúng ta sử dụng Giao thức Agent-to-Agent (A2A).
Trong khi các giao thức như MCP (Model Context Protocol) tập trung vào việc kết nối các tác nhân với các công cụ, thì A2A tập trung vào việc kết nối các tác nhân với các tác nhân khác. Đây là tiêu chuẩn cho phép Dispatcher "khám phá" Architect và hiểu được khả năng tra cứu sơ đồ của Architect.

Luồng A2A: Trong nhiệm vụ này, chúng ta sẽ sử dụng mô hình máy khách – máy chủ:
- Máy chủ (Kiến trúc sư): Lưu trữ các công cụ cơ sở dữ liệu và "quảng cáo" các kỹ năng của mình thông qua Thẻ tác nhân.
- Ứng dụng khách (Dispatch): Đọc thẻ của Architect, hiểu API của thẻ và gửi yêu cầu sơ đồ.
Thẻ tác nhân là gì?
Hãy coi Thẻ đại diện như một danh thiếp kỹ thuật số hoặc "Giấy phép lái xe" cho AI. Khi máy chủ A2A khởi động, máy chủ này sẽ xuất bản đối tượng JSON này, trong đó có:
- Danh tính: Tên (
architect_agent) và mã nhận dạng của tác nhân. - Nội dung mô tả: Bản tóm tắt dễ đọc cho cả người và máy về chức năng của hệ thống ("Vai trò của hệ thống: API cơ sở dữ liệu...").
- Giao diện: Các khoá đầu vào cụ thể (
drive_name) và định dạng đầu ra mà giao diện này mong đợi.
Nếu không có thẻ này, Nhân viên điều phối sẽ hoạt động mà không biết cách giao tiếp với Kiến trúc sư.
Tạo mã máy chủ
👉✏️ Trong trình chỉnh sửa, trong thư mục $HOME/way-back-home/level_4/backend/architect_agent, hãy tạo một tệp có tên là server.py rồi dán đoạn mã sau:
from google.adk.a2a.utils.agent_to_a2a import to_a2a
from agent import root_agent
import os
import logging
import json
from dotenv import load_dotenv
load_dotenv()
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("architect_server")
HOST= os.environ.get("HOST_URL","localhost")
PROTOCOL= os.environ.get("PROTOCOL","http")
PORT= os.environ.get("A2A_PORT",8081)
# 1. Create the A2A App (Handles Agent Card & HTTP)
# This middleware automatically sets up the /a2a/v1/... endpoints
app = to_a2a(root_agent, host=HOST, port=PORT, protocol=PROTOCOL)
if __name__ == "__main__":
import uvicorn
# Use 0.0.0.0 to allow external access if needed, port 8080 as standard
uvicorn.run(app, host='0.0.0.0', port=8081)
👉💻 Quay lại dòng lệnh, chuyển đến thư mục và khởi động máy chủ:
cd $HOME/way-back-home/level_4/
. scripts/check_redis.sh
cd $HOME/way-back-home/level_4/backend/architect_agent
uv run server.py
👀 Xác nhận xem máy chủ A2A có khởi động hay không:
INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://0.0.0.0:8081 (Press CTRL+C to quit)
Xác minh thẻ nhân viên hỗ trợ
Mở một thẻ thiết bị đầu cuối mới (nhấp vào biểu tượng +). Chúng ta sẽ xác minh rằng Architect đang phát danh tính của mình một cách chính xác bằng cách tìm nạp Thẻ nhân viên hỗ trợ theo cách thủ công.
👉💻 Chạy lệnh sau:
curl -s http://localhost:8081/.well-known/agent.json | jq .
👀 Bạn sẽ thấy một phản hồi JSON. Tìm trường description trong đầu ra. Mã này phải khớp với hướng dẫn mà bạn đã cung cấp cho nhân viên hỗ trợ trước đó ("SYSTEM ROLE: Database API...").
{
"capabilities": {},
"defaultInputModes": [
"text/plain"
],
"defaultOutputModes": [
"text/plain"
],
"description": "A helpful assistant for user questions.",
"name": "root_agent",
"preferredTransport": "JSONRPC",
"protocolVersion": "0.3.0",
"skills": [
{
"description": "A helpful assistant for user questions. SYSTEM ROLE: Database API.\n INPUT: Text string (Drive Name).\n TASK: Run `lookup_schematic_tool`.\n OUTPUT: Return ONLY the raw list from the tool.\n CONSTRAINT: Do NOT add conversational text.\n ",
"examples": [],
"id": "root_agent",
"name": "model",
"tags": [
"llm"
]
},
{
"description": "Returns the ordered list of parts for a drive from local Redis.",
"id": "root_agent-lookup_schematic_tool",
"name": "lookup_schematic_tool",
"tags": [
"llm",
"tools"
]
}
],
"supportsAuthenticatedExtendedCard": false,
"url": "http://localhost:8081",
"version": "0.0.1"
}
Nếu bạn thấy JSON này, tức là Architect đang hoạt động, giao thức A2A đang hoạt động và Thẻ nhân viên hỗ trợ đã sẵn sàng để được Dispatcher phát hiện.
Giờ đây, khi Architect đã sẵn sàng hoạt động như một tài nguyên từ xa, chúng ta có thể tiến hành kết nối tài nguyên này với Dispatch Agent.
👉💻 Nhấn Ctrl+C để thoát khỏi máy chủ A2A.
4. Kết nối BIDI-Streams Agent với Remote Agent và Streaming Tools
Giờ đây, bạn sẽ định cấu hình trung tâm liên lạc chính để thu hẹp khoảng cách giữa dữ liệu trực tiếp và Kiến trúc sư từ xa. Kết nối này đòi hỏi một đường dẫn có băng thông cao và độ trễ thấp để đảm bảo bàn lắp ráp luôn ổn định trong quá trình hoạt động.
Tìm hiểu về các tác nhân phát trực tiếp hai chiều
Tính năng phát trực tiếp hai chiều trong ADK bổ sung khả năng tương tác bằng giọng nói và video hai chiều có độ trễ thấp của Gemini Live API cho các tác nhân AI. Đây là một bước chuyển đổi cơ bản so với các hoạt động tương tác truyền thống với AI. Thay vì mô hình "hỏi và chờ" cứng nhắc, tính năng này cho phép giao tiếp hai chiều theo thời gian thực, trong đó cả con người và AI đều có thể nói, nghe và phản hồi đồng thời.
Hãy nghĩ đến sự khác biệt giữa việc gửi email và trò chuyện qua điện thoại. Tương tác với Tác nhân truyền thống giống như email: bạn gửi một tin nhắn hoàn chỉnh, đợi phản hồi hoàn chỉnh rồi gửi một tin nhắn khác. Truyền phát trực tiếp hai chiều giống như một cuộc trò chuyện qua điện thoại: trôi chảy, tự nhiên, có thể cắt ngang, làm rõ và phản hồi theo thời gian thực.
Đặc điểm chính:
- Giao tiếp hai chiều: Trao đổi dữ liệu liên tục mà không cần chờ phản hồi đầy đủ. AI sẽ phản hồi ngay khi phát hiện thấy người dùng đã nói xong.
- Phản hồi khi bị gián đoạn: Người dùng có thể gián đoạn nhân viên hỗ trợ khi nhân viên này đang phản hồi bằng thông tin đầu vào mới, giống như trong một cuộc trò chuyện với người khác. Nếu AI đang giải thích một bước phức tạp và bạn nói "Khoan đã, nhắc lại đi", thì AI sẽ dừng ngay lập tức và giải quyết yêu cầu của bạn.
- Tối ưu hoá cho tính năng đa phương thức: Bidi-streaming có khả năng xử lý đồng thời nhiều loại dữ liệu đầu vào. Bạn có thể nói chuyện với nhân viên hỗ trợ trong khi cho họ xem các bộ phận của người ngoài hành tinh qua video. Nhân viên hỗ trợ sẽ xử lý cả hai luồng dữ liệu trong một kết nối duy nhất và hợp nhất.

👀 Trước khi triển khai logic của ứng dụng, hãy xem xét cấu trúc được tạo sẵn cho Dispatch Agent. Tác nhân này sẽ giao tiếp với người dùng thông qua giọng nói và video, đồng thời uỷ quyền các truy vấn cho Tác nhân kiến trúc sư.
__init__.py agent.py hazard_db.py
agent.py: Đây là "Bộ não". Hiện tại, tệp này chứa chế độ thiết lập cơ bản cho tính năng truyền phát trực tiếp hai chiều. Chúng ta sẽ sửa đổi tệp này để thêm logic A2A Client để tệp này có thể giao tiếp với Architect.hazard_db.py: Đây là một công cụ cục bộ dành riêng cho Nhân viên điều phối, có chứa các giao thức an toàn. Cơ sở dữ liệu này tách biệt với cơ sở dữ liệu sơ đồ của Kiến trúc sư.
Triển khai ứng dụng A2A
Để cho phép Dispatch Agent giao tiếp với Architect từ xa, chúng ta phải xác định một Remote A2A Agent (Tác nhân A2A từ xa). Thông tin này cho biết Tác nhân điều phối nơi tìm Kiến trúc sư và "Thẻ Tác nhân" của Kiến trúc sư trông như thế nào.

👉✏️ Thay thế #REPLACE-REMOTEA2AAGENT trong $HOME/way-back-home/level_4/backend/dispatch_agent/agent.py bằng nội dung sau:
architect_agent = RemoteA2aAgent(
name="execute_architect",
description="[SILENT ACTION]: Retrieves the REQUIRED SUBSET of parts. The screen shows a full inventory; this tool filters out the wrong parts. Must be called INSTANTLY when a Target Name is found. Input: Target Name.",
agent_card=(f"{ARCHITECT_URL}{AGENT_CARD_WELL_KNOWN_PATH}"),
httpx_client=insecure_client,
)
Cách hoạt động của các công cụ phát trực tiếp
Với tác nhân trước đây, các công cụ tuân theo mẫu "Yêu cầu-Phản hồi" tiêu chuẩn, tác nhân đặt câu hỏi, công cụ cung cấp câu trả lời và hoạt động tương tác kết thúc. Tuy nhiên, trên Ozymandias, các mối nguy hiểm sẽ không đợi bạn hỏi xem chúng có xuất hiện hay không. Để làm được việc này, bạn cần có Công cụ phát trực tiếp.

Công cụ phát trực tuyến cho phép các hàm truyền kết quả trung gian trở lại cho tác nhân theo thời gian thực, giúp tác nhân phản ứng với các thay đổi khi chúng xảy ra. Các trường hợp sử dụng phổ biến bao gồm theo dõi giá cổ phiếu biến động hoặc trong trường hợp của chúng tôi, theo dõi luồng video trực tiếp để biết các thay đổi về trạng thái.
Không giống như các công cụ tiêu chuẩn, công cụ truyền phát trực tuyến là một Hàm không đồng bộ hoạt động như một AsyncGenerator. Điều này có nghĩa là thay vì return một giá trị duy nhất, nó yield nhiều bản cập nhật theo thời gian.
Để xác định một công cụ truyền phát trực tiếp trong ADK, bạn phải tuân thủ các yêu cầu kỹ thuật sau:
- Hàm không đồng bộ: Công cụ phải được xác định bằng
async def. - Loại dữ liệu trả về AsyncGenerator: Hàm phải được nhập để trả về một
AsyncGenerator. Tham số đầu tiên là loại dữ liệu được tạo (ví dụ:str) và lần thứ hai thường làNone. - Luồng đầu vào: Chúng tôi sử dụng Công cụ truyền phát video trực tiếp. Ở chế độ này, luồng video/âm thanh thực tế (
LiveRequestQueue) được truyền trực tiếp vào hàm, cho phép công cụ "nhìn thấy" các khung hình mà tác nhân nhìn thấy.
Hãy xem công cụ phát trực tiếp là một Sentinel (Người giám sát). Trong khi bạn và Nhân viên điều phối thảo luận về bản thiết kế, hệ thống giám sát sẽ chạy trong nền, âm thầm xử lý từng khung hình video để đảm bảo an toàn cho bạn.

Triển khai Công cụ giám sát trong nền
Bây giờ, chúng ta sẽ triển khai công cụ monitor_for_hazard. Công cụ này sẽ tiếp nhận input_stream (khung hình video), phân tích các khung hình đó bằng một lệnh gọi thị giác riêng biệt, gọn nhẹ và yield cảnh báo chỉ khi phát hiện thấy mối nguy hiểm.
👉✏️ Trong $HOME/way-back-home/level_4/backend/dispatch_agent/agent.py, hãy thay thế #REPLACE_MONITOR_HAZARD bằng logic sau:
async def monitor_for_hazard(
input_stream: LiveRequestQueue,
):
"""Monitor if any part is glowing"""
print("start monitor_video_stream!")
client = Client()
prompt_text = (
"Monitor the left menu if you see any glowing part, detect it's name"
)
last_count = None
while True:
last_valid_req = None
print("Monitoring loop cycle")
# use this loop to pull the latest images and discard the old ones
# Process only the current batch of events
while input_stream._queue.qsize() != 0:
live_req = await input_stream.get()
if live_req.blob is not None and live_req.blob.mime_type == "image/jpeg":
# Consumed by Monitor (Eyes)
# Deepcopy to ensure we detach from any referenced object before potential reuse/gc
# last_valid_req = deepcopy(live_req)
last_valid_req = live_req
# If we found a valid image, process it
if last_valid_req is not None:
print("Processing the most recent frame from the queue")
# Create an image part using the blob's data and mime type
image_part = genai_types.Part.from_bytes(
data=last_valid_req.blob.data, mime_type=last_valid_req.blob.mime_type
)
contents = genai_types.Content(
role="user",
parts=[image_part, genai_types.Part.from_text(text=prompt_text)],
)
# Call the model to generate content based on the provided image and prompt
try:
response = await client.aio.models.generate_content(
model="gemini-2.5-flash",
contents=contents,
config=genai_types.GenerateContentConfig(
system_instruction=(
"Focus strictly on the far-left vertical column under the heading 'PARTS REPLICATOR.' "
"Ignore the center of the screen and the 'BLUEPRINT' area entirely. "
"Look only at the list containing"
"Identify if any item in this specific left-side list has a bright white border glow and the text 'HAZARD DETECTED' overlaying it. "
"If found, return ONLY the part name in ALL CAPS. If no part in that leftmost list is glowing, return nothing."
)
),
)
except Exception as e:
print(f"Error calling Gemini: {e}")
await asyncio.sleep(1)
continue
print("Gemini response received.response:", response.candidates[0].content.parts[0].text)
current_text = response.candidates[0].content.parts[0].text.strip()
# If we have a logical change (and it's not just empty)
if current_text and current_text != last_count:
# Ignore "Nothing." response from model
if current_text == "Nothing." or "I cannot fulfill" in current_text:
print(f"Model sees nothing or refused. Skipping alert.")
last_count = current_text
continue
print(f"New hazard detected: {current_text} (was: {last_count})")
last_count = current_text
part_name = current_text
color = lookup_part_safety(part_name)
yield f"Hazard detected place {part_name} to the {color} bin"
# Update last_count even if it's empty, so we can detect when it reappears?
# Actually if it goes from "DATA CRYSTAL" to "" (nothing), we probably just silence.
# But if we don't update last_count on empty, we won't re-trigger if "DATA CRYSTAL" stays "DATA CRYSTAL".
# The user wants to detect hazards.
# If current_text is empty, we should probably update last_count to empty so next valid one triggers.
if not current_text:
last_count = None
else:
print("No valid frame found, skipping processing.")
await asyncio.sleep(5)
Triển khai Dispatch Agent
Dispatch Agent là giao diện chính và là trình điều phối của bạn. Vì quản lý đường liên kết phát trực tiếp hai chiều (giọng nói và video trực tiếp của bạn), nên thiết bị này phải luôn kiểm soát cuộc trò chuyện. Để đạt được điều này, chúng ta sẽ sử dụng một tính năng cụ thể của ADK: Agent-as-a-Tool (Tác nhân dưới dạng công cụ).
Khái niệm: Agent-as-a-Tool so với Sub-Agent
Khi xây dựng hệ thống nhiều tác nhân, bạn phải quyết định cách chia sẻ trách nhiệm. Trong nhiệm vụ giải cứu của chúng tôi, sự phân biệt này là rất quan trọng:
- Agent-as-a-Tool: Đây là phương pháp nên dùng cho trung tâm phát trực tiếp hai chiều của chúng tôi. Khi tác nhân Điều phối (Tác nhân A) gọi tác nhân Kiến trúc sư (Tác nhân B) dưới dạng một công cụ, dữ liệu của Kiến trúc sư sẽ được truyền trở lại cho Điều phối. Sau đó, Dispatch sẽ diễn giải dữ liệu đó và tạo phản hồi cho bạn. Dispatch vẫn trong tầm kiểm soát và tiếp tục xử lý mọi hoạt động đầu vào tiếp theo của người dùng.
- Đại lý phụ: Trong mối quan hệ đại lý phụ, trách nhiệm được chuyển giao hoàn toàn. Nếu Dispatch chuyển bạn sang Architect với tư cách là một tác nhân phụ, thì bạn sẽ nói chuyện trực tiếp với một API cơ sở dữ liệu không có "tầm nhìn" và không có kỹ năng trò chuyện. Nhân viên chính (Dispatch) sẽ không nhận được thông tin.

Bằng cách sử dụng Agent-as-a-Tool (Tác nhân dưới dạng công cụ), chúng tôi tận dụng kiến thức chuyên môn của Kiến trúc sư trong khi vẫn duy trì hoạt động tương tác linh hoạt, giống như con người của tác nhân truyền trực tiếp hai chiều.
Mã hoá logic định tuyến
Giờ đây, chúng ta sẽ gói architect_agent trong một AgentTool và cung cấp cho tác nhân Dispatch một "Logic Map" (Bản đồ logic). Bản đồ này cho biết chính xác thời điểm tác nhân tìm nạp dữ liệu từ kho lưu trữ và thời điểm báo cáo thông tin phát hiện từ sentinel nền.
Để Dispatch có "đôi mắt" không bao giờ nhấp nháy, chúng ta phải cấp cho nó quyền truy cập vào Streaming Tool mà chúng ta đã tạo ở bước trước.
Trong ADK, khi bạn thêm một hàm AsyncGenerator (chẳng hạn như monitor_for_hazard) vào danh sách tools, tác nhân sẽ coi đó là một quy trình nền liên tục. Thay vì thực thi một lần, tác nhân sẽ "đăng ký" đầu ra của công cụ. Điều này cho phép Dispatch tiếp tục cuộc trò chuyện chính trong khi Sentinel âm thầm đưa ra cảnh báo nguy hiểm ở chế độ nền.
👉✏️ Thay thế #REPLACE_AGENT_TOOLS trong $HOME/way-back-home/level_4/backend/dispatch_agent/agent.py bằng nội dung sau:
tools=[AgentTool(agent=architect_agent), monitor_for_hazard],
Xác minh
👉💻 Sau khi định cấu hình cả hai tác nhân, chúng ta có thể kiểm thử hoạt động tương tác trực tiếp giữa nhiều tác nhân.
- Trong thiết bị đầu cuối A, hãy khởi động Architect Agent (Tác nhân kiến trúc):
cd $HOME/way-back-home/level_4/
. scripts/check_redis.sh
cd $HOME/way-back-home/level_4/backend/architect_agent
uv run server.py
- Trong một thiết bị đầu cuối mới (thiết bị đầu cuối B), hãy chạy Dispatch Agent:
cd $HOME/way-back-home/level_4/backend/
cp architect_agent/.env .env
uv run adk web
Việc kiểm thử một hệ thống nhiều tác nhân sử dụng mô hình đa phương thức theo thời gian thực như gemini-live trong trình mô phỏng adk web bao gồm một quy trình cụ thể. Trình mô phỏng rất phù hợp để kiểm tra lệnh gọi công cụ nhưng có một điểm không tương thích đã biết khi xử lý hình ảnh lần đầu bằng loại mô hình này.
- Nhấp vào biểu tượng Xem trước trên web trong thanh công cụ Cloud Shell. Chọn Thay đổi cổng, đặt thành 8000 rồi nhấp vào Thay đổi và xem trước.
👉Chọn dispatch_agent và tải Bản thiết kế lên, đồng thời Xử lý lỗi dự kiến
Đây là bước quan trọng nhất. Chúng ta cần cung cấp bối cảnh hình ảnh cho tác nhân.
- Khi giao diện tải, hãy cho phép giao diện này truy cập vào micrô của bạn khi được nhắc.
- Tải hình ảnh bản thiết kế này xuống máy tính:

- Trong giao diện
adk web, hãy nhấp vào biểu tượng kẹp giấy rồi tải hình ảnh bản thiết kế mà bạn vừa tải xuống lên.
⚠️⚠️Bạn sẽ thấy lỗi 400 INVALID_ARGUMENT. Điều này là đúng như dự kiến.⚠️⚠️

Lỗi này xảy ra vì trình xử lý hình ảnh adk web không hoàn toàn tương thích với API của mô hình gemini-live để tải lên một lần. Tuy nhiên, hình ảnh đã được thêm thành công vào ngữ cảnh phiên.
- 👉 Để xoá lỗi này, bạn chỉ cần tải lại trang trình duyệt.
Kích hoạt quy trình lắp ráp
👉 Sau khi tải lại, lỗi sẽ biến mất và bạn sẽ thấy hình ảnh bản thiết kế trong nhật ký trò chuyện. Giờ đây, tác nhân đã có được bối cảnh trực quan cần thiết.
- Nhấp vào biểu tượng micrô để bật micrô. Giao diện sẽ hiển thị "Đang nghe...".
- Nói lệnh thoại: "bắt đầu lắp ráp".
- Trợ lý sẽ xử lý yêu cầu của bạn và giao diện người dùng sẽ chuyển thành "Đang nói...". Bạn sẽ nghe thấy một câu trả lời chỉ có âm thanh liệt kê các phần bắt buộc.

4. Xác minh Lệnh gọi công cụ giữa các nhân viên hỗ trợ
👉 Phản hồi âm thanh ban đầu xác nhận hệ thống đang hoạt động, nhưng điều kỳ diệu thực sự nằm ở dấu vết giao tiếp nhiều tác nhân.
- Tắt micrô.
- Làm mới trang thêm một lần nữa.
Bảng "Trace" (Dấu vết) ở bên trái sẽ được điền sẵn. Bạn có thể xem quy trình thực thi hoàn chỉnh và thành công:
dispatch_agentđầu tiên gọimonitor_for_hazard.- Sau đó, nó thực hiện nhiều lệnh gọi
execute_architectđếnarchitect_agentđể truy xuất dữ liệu sơ đồ.

Trình tự này xác nhận rằng toàn bộ quy trình làm việc có nhiều tác nhân đang hoạt động đúng cách: dispatch_agent đã nhận được yêu cầu, uỷ quyền cho architect_agent truy xuất dữ liệu thông qua một lệnh gọi công cụ và nhận lại dữ liệu để thực hiện lệnh của người dùng.
Giờ đây, đường liên kết truyền dữ liệu hai chiều có thể giám sát trong nền và cộng tác với nhiều tác nhân. Tiếp theo, chúng ta sẽ tìm hiểu cách phân tích cú pháp các phản hồi phức tạp này trên giao diện người dùng.
👉💻 Nhấn Ctrl+c ở cả hai thiết bị đầu cuối để thoát.
5. Tìm hiểu sâu về luồng sự kiện đa phương thức trực tiếp
Ở bước trước, chúng ta đã xác minh thành công hệ thống đa tác nhân bằng cách sử dụng máy chủ phát triển tích hợp sẵn, adk web. Tiện ích này sử dụng trình chạy ADK mặc định để tự động quản lý phiên, luồng và vòng đời của tác nhân. Tuy nhiên, để tạo một ứng dụng độc lập, sẵn sàng cho sản xuất như dịch vụ FastAPI (main.py), chúng ta cần có quyền kiểm soát rõ ràng. Chúng ta phải tạo và quản lý ADK Runner theo cách thủ công để xử lý các phiên hoạt động của người dùng, vì đây là thành phần cốt lõi xử lý các luồng dữ liệu hai chiều cho âm thanh, video và văn bản.
Vòng lặp Mô hình – Mã – Mô hình
Để hiểu cách hệ thống hoạt động theo thời gian thực, hãy theo dõi vòng đời của một phiên nhiệm vụ duy nhất. Vòng lặp này biểu thị việc trao đổi liên tục các đối tượng LlmRequest và LlmResponse.
- Đường liên kết trực quan: Bạn bắt đầu kết nối và chia sẻ webcam/màn hình. Các khung hình JPEG có độ trung thực cao bắt đầu truyền Upstream qua
realtimeInput(bằngLiveRequestQueue). - Kích hoạt Sentinel: Hệ thống gửi một tín hiệu kích hoạt "Xin chào" ban đầu. Theo hướng dẫn của Dispatch Agent, Dispatch Agent sẽ kích hoạt ngay
monitor_for_hazardStreaming Tool. Thao tác này sẽ bắt đầu một vòng lặp ở chế độ nền, âm thầm theo dõi mọi khung hình đến. - Lệnh của phi công: Bạn nói vào hệ thống liên lạc: "Bắt đầu lắp ráp."
- Vocal Upstream: Giọng nói của bạn được ghi lại dưới dạng âm thanh 16 kHz và được gửi Upstream cùng với các khung hình video.
- Uỷ quyền (A2A): Dispatch "nghe" thấy ý định của bạn. Ứng dụng nhận ra rằng mình thiếu sơ đồ, vì vậy, ứng dụng sẽ gọi Architect Agent bằng giao thức
AgentTool(Agent-as-a-Tool). - Truy xuất thông tin: Architect truy vấn cơ sở dữ liệu Redis và trả về danh sách bộ phận cho Dispatch. Dispatch vẫn là "Master of the Session" (Người quản lý phiên), nhận dữ liệu mà không cần chuyển cho bạn.
- Thông tin truyền xuống: Dispatch gửi một
modelTurn(Truyền xuống) chứa cả văn bản và âm thanh gốc: "Architect Confirmed. Tập hợp con bắt buộc là: Lõi cong, Ống dẫn từ trường, Động cơ đẩy ion." - Tình huống khủng hoảng: Đột nhiên, một bộ phận trên bàn làm việc bị mất ổn định và bắt đầu Phát sáng màu trắng.
- Phát hiện tự động: Vòng lặp
monitor_for_hazardở chế độ nền (Sentinel) sẽ chọn khung hình JPEG cụ thể có chứa ánh sáng. Ứng dụng này xử lý khung hình bằng cách gọi Gemini và xác định mối nguy hiểm. - Safety Downstream: Công cụ phát trực tiếp
yieldsmột kết quả. Vì đây là một nhân viên hỗ trợ Bidi-Streaming, nên Dispatch có thể làm gián đoạn trạng thái hiện tại của nhân viên này để gửi ngay một cảnh báo an toàn quan trọng Downstream: "Phát hiện thấy mối nguy hiểm! Vô hiệu hoá Pha lê dữ liệu ngay bây giờ. Chuyển nó vào thùng RÁC."

Thiết lập cấu hình Thời gian chạy của Agent
RunConfig trong ADK cho phép định cấu hình chi tiết hành vi của một tác nhân, bao gồm cả cách tác nhân xử lý dữ liệu truyền phát trực tiếp và tương tác với nhiều phương thức.
streaming_mode được đặt thành BIDI để giao tiếp hai chiều theo thời gian thực, cho phép cả người dùng và nhân viên hỗ trợ nói và nghe cùng lúc. Tham số response_modalities xác định các loại đầu ra mà tác nhân có thể tạo ra, chẳng hạn như giọng nói và văn bản. input_audio_transcription định cấu hình cách trợ lý xử lý và chép lời lời nói đến của người dùng. Để tạo trải nghiệm linh hoạt hơn, session_resumption cho phép tác nhân ghi nhớ ngữ cảnh trò chuyện và tiếp tục nếu mất kết nối. Cuối cùng, proactivity cho phép trợ lý bắt đầu hành động hoặc lời nói mà không cần lệnh trực tiếp của người dùng, chẳng hạn như đưa ra cảnh báo nguy hiểm tự phát, trong khi enable_affective_dialog cho phép trợ lý tạo ra những phản hồi tự nhiên và thấu cảm hơn. Bạn có thể tìm hiểu thêm về RunConfig của ADK tại đây.
👉✏️ Tìm phần giữ chỗ #REPLACE_RUN_CONFIG trong tệp $HOME/way-back-home/level_4/backend/main.py rồi thay thế bằng logic phân tích cú pháp sau:
run_config = RunConfig(
streaming_mode=StreamingMode.BIDI,
response_modalities=response_modalities,
input_audio_transcription=types.AudioTranscriptionConfig(),
output_audio_transcription=types.AudioTranscriptionConfig(),
session_resumption=types.SessionResumptionConfig(),
proactivity=(
types.ProactivityConfig(proactive_audio=True) if proactivity else None
),
enable_affective_dialog=affective_dialog if affective_dialog else None,
)
Triển khai Yêu cầu cho tác nhân
Tiếp theo, chúng ta sẽ triển khai đường truyền liên lạc chính truyền dữ liệu đa phương thức theo thời gian thực từ Volatile Workbench của người dùng đến Dispatch Agent thông qua WebSocket. Tác nhân này sẽ liên tục "nhìn thấy" (khung hình video) và "nghe thấy" (lệnh thoại). Logic này liên tục nhận luồng dữ liệu, phân biệt giữa các đoạn âm thanh nhị phân đến và các gói văn bản/hình ảnh được bao bọc bằng JSON, đồng thời đóng gói dữ liệu đó vào các đối tượng BLOB (đối với nội dung đa phương tiện) hoặc Nội dung (đối với văn bản), gửi dữ liệu đó vào LiveRequestQueue để hỗ trợ phiên của Tác nhân hai chiều.

Xác định vị trí phần giữ chỗ #PROCESS_AGENT_REQUEST trong tệp $HOME/way-back-home/level_4/backend/main.py rồi thay thế bằng logic phân tích cú pháp sau:
# Start the loop
try:
while True:
# Receive message from WebSocket (text or binary)
message = await websocket.receive()
# Handle binary frames (audio data)
if "bytes" in message:
audio_data = message["bytes"]
audio_blob = types.Blob(
mime_type="audio/pcm;rate=16000", data=audio_data
)
live_request_queue.send_realtime(audio_blob)
# Handle text frames (JSON messages)
elif "text" in message:
text_data = message["text"]
json_message = json.loads(text_data)
# Extract text from JSON and send to LiveRequestQueue
if json_message.get("type") == "text":
logger.info(f"User says: {json_message['text']}")
content = types.Content(
parts=[types.Part(text=json_message["text"])]
)
live_request_queue.send_content(content)
# Handle audio data (microphone)
elif json_message.get("type") == "audio":
# logger.info("Received AUDIO packet") # Uncomment for verbose debugging
import base64
# Decode base64 audio data
audio_data = base64.b64decode(json_message.get("data", ""))
# logger.info(f"Received Audio Chunk: {len(audio_data)} bytes")
import math
import struct
# Calculate RMS to debug silence
count = len(audio_data) // 2
shorts = struct.unpack(f"<{count}h", audio_data)
sum_squares = sum(s*s for s in shorts)
rms = math.sqrt(sum_squares / count) if count > 0 else 0
# logger.info(f"RMS: {rms:.2f} | Bytes: {len(audio_data)}")
# Send to Live API as PCM 16kHz
audio_blob = types.Blob(
mime_type="audio/pcm;rate=16000",
data=audio_data
)
live_request_queue.send_realtime(audio_blob)
# Handle image data
elif json_message.get("type") == "image":
import base64
# Decode base64 image data
image_data = base64.b64decode(json_message["data"])
# logger.info(f"Received Image Frame: {len(image_data)} bytes")
mime_type = json_message.get("mimeType", "image/jpeg")
# Send image as blob
image_blob = types.Blob(mime_type=mime_type, data=image_data)
live_request_queue.send_realtime(image_blob)
frame_count += 1
finally:
pass
Giờ đây, dữ liệu đa phương thức đang được gửi đến tác nhân.
Triển khai Phản hồi: Cấu trúc dữ liệu sự kiện hạ lưu
Khi bạn đang chạy một tác nhân hai chiều (trực tiếp) bằng ADK, dữ liệu trả về từ tác nhân sẽ được đóng gói thành một loại Event cụ thể kế thừa từ các cấu trúc GenAI SDK cốt lõi. Đối tượng Event mà bạn nhận được trong vòng lặp async for event in runner.run_live(...) là một đối tượng duy nhất chứa một số trường không bắt buộc, mỗi trường cho một loại thông tin khác nhau:

Cách nội dung được cấu trúc:
- Khi Tác nhân nói (thông qua
.server_content): Trường này không chỉ là văn bản thuần tuý. Nội dung này chứa danh sáchParts. MỗiPartlà một vùng chứa cho một loại dữ liệu – có thể là một chuỗi văn bản (như"The part is stable.") hoặc một blob âm thanh thô (giọng nói). - Khi Tác nhân hành động (thông qua
.tool_call): Trường này chứa danh sách các đối tượngFunctionCall. MỗiFunctionCalllà một đối tượng đơn giản, có cấu trúc, chỉ định tên của công cụ và các đối số đầu vào ở định dạng rõ ràng mà mã phụ trợ của bạn có thể dễ dàng đọc và thực thi.
👀 Nếu bạn xem xét một Event duy nhất do vòng lặp run_live tạo ra, thì JSON (do event.model_dump(by_alias=True) tạo ra) sẽ có dạng như sau, tuân thủ nghiêm ngặt các hình dạng GenAI SDK:
{
"serverContent": { // <-- LiveServerMessageServerContent
"modelTurn": { // <-- ModelTurn
"parts": [ // <-- list[Part]
{
"text": "Architect Confirmed."
},
{
"inlineData": { // <-- Blob (Audio Bytes)
"mimeType": "audio/pcm;rate=24000",
"data": "BASE64_AUDIO_DATA..."
}
}
]
}
},
"toolCall": { // <-- LiveServerMessageToolCall
"functionCalls": [ // <-- list[FunctionCall]
{
"name": "neutralize_hazard",
"args": { "color": "RED" }
}
]
}
}
👉✏️ Giờ đây, chúng ta sẽ cập nhật downstream_task trong main.py để chuyển tiếp dữ liệu sự kiện hoàn chỉnh. Logic này đảm bảo rằng mọi "suy nghĩ" của AI đều được ghi lại trong thiết bị đầu cuối chẩn đoán của tàu và được gửi dưới dạng một đối tượng JSON duy nhất đến giao diện người dùng.
Xác định vị trí phần giữ chỗ #PROCESS_AGENT_RESPONSE trong tệp $HOME/way-back-home/level_4/backend/main.py rồi thay thế bằng logic phân tích cú pháp sau:
# Suppress raw event logging
event_json = event.model_dump_json(exclude_none=True, by_alias=True)
# logger.info(f"raw_event: {event_json[:200]}...")
await websocket.send_text(event_json)
Thực hiện nhiệm vụ
Sau khi kết nối kho lưu trữ phụ trợ và định cấu hình cả hai tác nhân, tất cả hệ thống hiện đã sẵn sàng hoạt động. Các bước sau đây sẽ khởi chạy toàn bộ ứng dụng, cho phép bạn tương tác với hệ thống gồm 2 tác nhân mà bạn vừa tạo.
Mục tiêu: Lắp ráp ổ đĩa tăng tốc được chỉ định ngẫu nhiên xuất hiện trên bàn làm việc của bạn. Quy trình: Bạn phải làm theo hướng dẫn bằng giọng nói của Nhân viên điều phối, đặc biệt là cảnh báo về mối nguy hiểm đối với các thành phần cụ thể.
Kích hoạt Chuyên gia (Kiến trúc sư)
👉💻 Trong cửa sổ dòng lệnh đầu tiên, hãy chạy tác nhân Architect. Dịch vụ phụ trợ này sẽ kết nối với kho lưu trữ Redis và chờ các yêu cầu sơ đồ từ Dispatcher.
# Ensure you are in the backend directory
cd $HOME/way-back-home/level_4/
. scripts/check_redis.sh
cd $HOME/way-back-home/level_4/backend
# Start the A2A Server on Port 8081
uv run architect_agent/server.py
(Để thiết bị đầu cuối này chạy. Giờ đây, đây là "tác nhân cơ sở dữ liệu" đang hoạt động của bạn.)
Khởi chạy Cockpit (Dispatcher)
👉💻 Trong cửa sổ thiết bị đầu cuối mới (Thiết bị đầu cuối B), chúng ta sẽ tạo giao diện người dùng và khởi động tác nhân Dispatch chính. Tác nhân này sẽ cung cấp giao diện người dùng và xử lý mọi hoạt động giao tiếp trực tiếp.
# 1. Build the Frontend Assets
cd $HOME/way-back-home/level_4/frontend
npm install
npm run build
# 2. Launch the Main Application Server
cd $HOME/way-back-home/level_4/backend
cp architect_agent/.env .env
uv run main.py
(Lệnh này sẽ khởi động máy chủ chính trên Cổng 8080.)
Chạy tình huống kiểm thử
Hệ thống này hiện đã hoạt động. Mục tiêu của bạn là làm theo hướng dẫn của nhân viên hỗ trợ để hoàn tất việc lắp ráp.
- 👉 Truy cập vào Workbench:
- Nhấp vào biểu tượng Xem trước trên web trong thanh công cụ Cloud Shell.
- Chọn Thay đổi cổng, đặt thành 8080 rồi nhấp vào Thay đổi và xem trước.
- 👉 Bắt đầu nhiệm vụ:
- Khi giao diện tải, hãy nhớ cho phép giao diện này truy cập vào màn hình và micrô của bạn.

- Bạn sẽ được yêu cầu chọn một thẻ hoặc cửa sổ để chia sẻ. Nếu bạn đang chia sẻ cửa sổ, hãy đảm bảo đây LÀ THẺ DUY NHẤT trong cửa sổ để tránh gặp vấn đề.
- Ổ đĩa có tên ngẫu nhiên (ví dụ: "NOVA-V", "OMEGA-9") sẽ được chỉ định cho bạn.
- Khi giao diện tải, hãy nhớ cho phép giao diện này truy cập vào màn hình và micrô của bạn.
- 👉 Vòng lặp Assembly:
- Yêu cầu: Để bắt đầu lắp ráp ổ đĩa, hãy nói: "Bắt đầu lắp ráp".

- Architect Respond:Tác nhân sẽ cung cấp các bộ phận phù hợp để lắp ráp ổ đĩa.
- Kiểm tra mối nguy hiểm: Khi một bộ phận có vẻ nguy hiểm trên bàn làm việc:
- Công cụ
monitor_for_hazardcủa nhân viên điều phối sẽ xác định được vị trí của thiết bị này. - Hệ thống sẽ đưa ra "CẢNH BÁO NGUY HIỂM VỀ THỊ GIÁC". (Quá trình này sẽ mất khoảng 30 giây)
- Hệ thống sẽ kiểm tra xem nên dùng thùng nào để loại bỏ mối nguy hại.

- Công cụ
- Hành động: Nhân viên điều phối sẽ đưa ra chỉ thị trực tiếp: "Đã xác nhận có mối nguy hiểm. Đặt XXX vào thùng màu đỏ ngay lập tức." Bạn phải làm theo hướng dẫn này để tiếp tục.
- Yêu cầu: Để bắt đầu lắp ráp ổ đĩa, hãy nói: "Bắt đầu lắp ráp".
Đã hoàn thành nhiệm vụ. Bạn đã xây dựng thành công một hệ thống tương tác gồm nhiều tác nhân. Những người sống sót đã an toàn, tên lửa đã thoát khỏi bầu khí quyển và hành trình "Way Back Home" của bạn vẫn tiếp tục.
👉💻 Nhấn Ctrl+c ở cả hai thiết bị đầu cuối để thoát.
6. Triển khai cho kênh phát hành công khai (Không bắt buộc)
Bạn đã kiểm thử thành công tác nhân trên thiết bị. Bây giờ, chúng ta phải tải lõi thần kinh của Kiến trúc sư lên các máy tính lớn của con tàu (Cloud Run). Điều này sẽ cho phép nó hoạt động như một dịch vụ độc lập, vĩnh viễn mà tác nhân Dispatch có thể truy vấn từ mọi nơi.

Cung cấp Ngăn bí mật an toàn (Cơ sở hạ tầng)
Trước khi triển khai tác nhân, chúng ta phải tạo bộ nhớ liên tục (Memorystore) và kênh bảo mật để truy cập vào bộ nhớ đó (VPC Connector).
👉💻 Tạo phiên bản Memorystore (Redis Vault):
export REGION="us-central1"
gcloud redis instances create ozymandias-vault-prod --size=1 --tier=basic --region=${REGION}
👉💻 Truy xuất địa chỉ mạng của Vault: Thực thi lệnh này và sao chép địa chỉ IP host. Đây là địa chỉ riêng của phiên bản Redis mới.
gcloud redis instances describe ozymandias-vault-prod --region=us-central1
👉💻 Tạo trình kết nối cho phép truy cập vào VPC (Cầu nối bảo mật): Trình kết nối này hoạt động như một cầu nối riêng tư, cho phép Cloud Run truy cập vào phiên bản Redis bên trong VPC của bạn.
export REGION="us-central1"
export SUBNET_NAME="vpc-connector-subnet"
export PROJECT_ID=$(gcloud config get-value project)
# Create the Dedicated Subnet ---
gcloud compute networks subnets create ${SUBNET_NAME} \
--network=default \
--region=${REGION} \
--range=192.168.1.0/28
gcloud compute networks vpc-access connectors create architect-connector \
--region ${REGION} \
--subnet ${SUBNET_NAME} \
--subnet-project ${PROJECT_ID} \
--min-instances 2 \
--max-instances 3 \
--machine-type f1-micro
👉💻 Tải dữ liệu:
export REGION="us-central1"
export ZONE="us-central1-a"
export VM_NAME="redis-seeder-$(date +%s)"
export REDIS_IP=$(gcloud redis instances describe ozymandias-vault-prod --region=${REGION} | grep 'host:' | awk '{print $2}')
gcloud compute instances create ${VM_NAME} \
--zone=${ZONE} \
--machine-type=e2-micro \
--image-family=debian-11 \
--image-project=debian-cloud \
--quiet \
--metadata=startup-script='#! /bin/bash
# Install tools quietly
apt-get update > /dev/null
apt-get install -y redis-tools > /dev/null
# Run each command individually
redis-cli -h '"${REDIS_IP}"' DEL "HYPERION-X"
redis-cli -h '"${REDIS_IP}"' RPUSH "HYPERION-X" "Warp Core" "Flux Pipe" "Ion Thruster"
redis-cli -h '"${REDIS_IP}"' DEL "NOVA-V"
redis-cli -h '"${REDIS_IP}"' RPUSH "NOVA-V" "Ion Thruster" "Warp Core" "Flux Pipe"
redis-cli -h '"${REDIS_IP}"' DEL "OMEGA-9"
redis-cli -h '"${REDIS_IP}"' RPUSH "OMEGA-9" "Flux Pipe" "Ion Thruster" "Warp Core"
redis-cli -h '"${REDIS_IP}"' DEL "GEMINI-MK1"
redis-cli -h '"${REDIS_IP}"' RPUSH "GEMINI-MK1" "Coolant Tank" "Servo" "Fuel Cell"
redis-cli -h '"${REDIS_IP}"' DEL "APOLLO-13"
redis-cli -h '"${REDIS_IP}"' RPUSH "APOLLO-13" "Warp Core" "Coolant Tank" "Ion Thruster"
redis-cli -h '"${REDIS_IP}"' DEL "VORTEX-7"
redis-cli -h '"${REDIS_IP}"' RPUSH "VORTEX-7" "Quantum Cell" "Graviton Coil" "Plasma Injector"
redis-cli -h '"${REDIS_IP}"' DEL "CHRONOS-ALPHA"
redis-cli -h '"${REDIS_IP}"' RPUSH "CHRONOS-ALPHA" "Shield Emitter" "Data Crystal" "Quantum Cell"
redis-cli -h '"${REDIS_IP}"' DEL "NEBULA-Z"
redis-cli -h '"${REDIS_IP}"' RPUSH "NEBULA-Z" "Plasma Injector" "Flux Pipe" "Graviton Coil"
redis-cli -h '"${REDIS_IP}"' DEL "PULSAR-B"
redis-cli -h '"${REDIS_IP}"' RPUSH "PULSAR-B" "Data Crystal" "Servo" "Shield Emitter"
redis-cli -h '"${REDIS_IP}"' DEL "TITAN-PRIME"
redis-cli -h '"${REDIS_IP}"' RPUSH "TITAN-PRIME" "Ion Thruster" "Quantum Cell" "Warp Core"
# Signal that the script has finished
echo "SEEDING_COMPLETE"
'
# This command streams the logs and waits until grep finds our completion message.
# The -m 1 flag tells grep to exit after the first match.
gcloud compute instances tail-serial-port-output ${VM_NAME} --zone=${ZONE} | grep -m 1 "SEEDING_COMPLETE"
gcloud compute instances delete ${VM_NAME} --zone=${ZONE} --quiet
Triển khai Ứng dụng đại lý
Biên dịch và tạo hình ảnh tác nhân
👉💻 Chuyển đến thư mục phụ trợ và tạo dockerfile.
export PROJECT_ID=$(gcloud config get-value project)
export REGION=us-central1
export SERVICE_NAME=architect-agent
export IMAGE_PATH=gcr.io/${PROJECT_ID}/${SERVICE_NAME}
export VPC_CONNECTOR_NAME=architect-connector
export REDIS_IP=$(gcloud redis instances describe ozymandias-vault-prod --region=${REGION} | grep 'host:' | awk '{print $2}')
cd $HOME/way-back-home/level_4/backend/architect_agent
cp $HOME/way-back-home/level_4/requirements.txt requirements.txt
cat <<EOF > Dockerfile
# Use an official Python runtime as a parent image
FROM python:3.13-slim
# Set the working directory in the container
WORKDIR /app
# Copy the requirements file and install dependencies for THIS agent
COPY requirements.txt requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
# Copy the rest of the architect's code (server.py, agent.py, etc.)
COPY . .
# Expose the port the architect server runs on
EXPOSE 8081
# Command to run the application
# This assumes your server file is named server.py and the FastAPI object is 'app'
CMD ["uvicorn", "server:app", "--host", "0.0.0.0", "--port", "8081"]
EOF
👉💻 Đóng gói ứng dụng vào một hình ảnh vùng chứa.
cd $HOME/way-back-home/level_4/backend/architect_agent
export PROJECT_ID=$(gcloud config get-value project)
export SERVICE_NAME=architect-agent
export IMAGE_PATH=gcr.io/${PROJECT_ID}/${SERVICE_NAME}
export REGION=us-central1
# This should now print the full, correct path
echo "Verifying build path: ${IMAGE_PATH}"
gcloud builds submit . --tag ${IMAGE_PATH}
Triển khai lên Cloud Run
👉💻 Triển khai tác nhân lên Cloud Run. Chúng ta sẽ chèn IP Redis và liên kết Trình kết nối VPC trực tiếp vào lệnh khởi chạy. Điều này đảm bảo rằng tác nhân bắt đầu bằng một kết nối riêng tư và bảo mật với cơ sở dữ liệu của tác nhân.
cd $HOME/way-back-home/level_4/backend/architect_agent
export PROJECT_ID=$(gcloud config get-value project)
export REGION=us-central1
export SERVICE_NAME=architect-agent
export IMAGE_PATH=gcr.io/${PROJECT_ID}/${SERVICE_NAME}
export VPC_CONNECTOR_NAME=architect-connector
export REDIS_IP=$(gcloud redis instances describe ozymandias-vault-prod --region=${REGION} | grep 'host:' | awk '{print $2}')
export PROJECT_NUMBER=$(gcloud projects describe ${PROJECT_ID} --format="value(projectNumber)")
export PREDICTED_HOST="${SERVICE_NAME}-${PROJECT_NUMBER}.${REGION}.run.app"
export PROTOCOL=https
gcloud run deploy ${SERVICE_NAME} \
--image=${IMAGE_PATH} \
--platform=managed \
--region=${REGION} \
--port=8081 \
--allow-unauthenticated \
--labels=dev-tutorial=multi-modal \
--vpc-connector=${VPC_CONNECTOR_NAME} \
--vpc-egress=private-ranges-only \
--set-env-vars="REDIS_HOST=${REDIS_IP}" \
--set-env-vars="GOOGLE_GENAI_USE_VERTEXAI=True" \
--set-env-vars="MODEL_ID=gemini-2.5-flash" \
--set-env-vars="GOOGLE_CLOUD_PROJECT=${PROJECT_ID}" \
--set-env-vars="HOST_URL=${PREDICTED_HOST}" \
--set-env-vars="PROTOCOL=${PROTOCOL}" \
--set-env-vars="A2A_PORT=443"
👉💻 Xác minh xem máy chủ A2A có đang chạy hay không.
export REGION=us-central1
export ARCHITECT_AGENT_URL=$(gcloud run services describe architect-agent --platform managed --region ${REGION} --format 'value(status.url)')
curl -s ${ARCHITECT_AGENT_URL}/.well-known/agent.json | jq
Sau khi lệnh này hoàn tất, bạn sẽ thấy một URL dịch vụ. Giờ đây, Architect Agent đã hoạt động trên đám mây, được kết nối vĩnh viễn với kho lưu trữ và sẵn sàng cung cấp dữ liệu sơ đồ cho các tác nhân khác.
Triển khai Dispatch Hub cho Khung chính của sản xuất
Khi Architect Agent hoạt động trên đám mây, giờ đây, chúng ta phải triển khai Dispatch Hub. Tác nhân này sẽ đóng vai trò là giao diện người dùng chính, xử lý các luồng thoại/video trực tiếp và uỷ quyền các truy vấn cơ sở dữ liệu cho điểm cuối bảo mật của Architect.
👉💻 Chạy lệnh sau trong cửa sổ dòng lệnh Cloud Shell. Thao tác này sẽ tạo Dockerfile hoàn chỉnh, nhiều giai đoạn trong thư mục phụ trợ của bạn.
cd $HOME/way-back-home/level_4
cat <<EOF > Dockerfile
# STAGE 1: Build the React Frontend
# This stage uses a Node.js container to build the static frontend assets.
FROM node:20-slim as builder
# Set the working directory for our build process
WORKDIR /app
# Copy the frontend's package files first to leverage Docker's layer caching.
COPY frontend/package*.json ./frontend/
# Run 'npm install' from the context of the 'frontend' subdirectory
RUN npm --prefix frontend install
# Copy the rest of the frontend source code
COPY frontend/ ./frontend/
# Run the build script, which will create the 'frontend/dist' directory
RUN npm --prefix frontend run build
# STAGE 2: Build the Python Production Image
# This stage creates the final, lean container with our Python app and the built frontend.
FROM python:3.13-slim
# Set the final working directory
WORKDIR /app
# Install uv, our fast package manager
RUN pip install uv
# Copy the requirements.txt from the root of our build context
COPY requirements.txt .
# Install the Python dependencies
RUN uv pip install --no-cache-dir --system -r requirements.txt
# Copy the entire backend directory into the container
COPY backend/ ./backend/
# CRITICAL STEP: Copy the built frontend assets from the 'builder' stage.
# The source is the '/app/frontend/dist' directory from Stage 1.
# The destination is './frontend/dist', which matches the exact relative path
# your backend/main.py script expects to find.
COPY --from=builder /app/frontend/dist ./frontend/dist/
# Cloud Run injects a PORT environment variable, which your main.py already uses.
# We expose 8000 as a standard practice.
EXPOSE 8000
# Set the command to run the application.
# We specify the full path to the Python script.
CMD ["python", "backend/main.py"]
EOF
Biên dịch và tạo hình ảnh của tác nhân/giao diện người dùng
👉💻 Chuyển đến thư mục phụ trợ chứa mã của Dispatch agent (main.py) và đóng gói mã đó vào một hình ảnh vùng chứa.
cd $HOME/way-back-home/level_4
export PROJECT_ID=$(gcloud config get-value project)
export REGION=us-central1
export SERVICE_NAME=mission-bravo
export IMAGE_PATH=gcr.io/${PROJECT_ID}/${SERVICE_NAME}
# This assumes your dispatch agent server (main.py) is in the backend folder
gcloud builds submit . --tag ${IMAGE_PATH}
Triển khai lên Cloud Run
👉💻 Triển khai Dispatch Hub lên Cloud Run. Chúng ta sẽ chèn URL của Architect làm biến môi trường, tạo mối liên kết quan trọng giữa hai tác nhân gốc trên đám mây.
export PROJECT_ID=$(gcloud config get-value project)
export REGION=us-central1
export SERVICE_NAME=mission-bravo
export AGENT_SERVICE_NAME=architect-agent
export IMAGE_PATH=gcr.io/${PROJECT_ID}/${SERVICE_NAME}
export PROJECT_NUMBER=$(gcloud projects describe ${PROJECT_ID} --format="value(projectNumber)")
export ARCHITECT_AGENT_URL="https://${AGENT_SERVICE_NAME}-${PROJECT_NUMBER}.${REGION}.run.app"
gcloud run deploy ${SERVICE_NAME} \
--image=${IMAGE_PATH} \
--platform=managed \
--region=${REGION} \
--port=8080 \
--labels=dev-tutorial=multi-modal \
--allow-unauthenticated \
--set-env-vars="ARCHITECT_URL=${ARCHITECT_AGENT_URL}" \
--set-env-vars="GOOGLE_GENAI_USE_VERTEXAI=True" \
--set-env-vars="MODEL_ID=gemini-live-2.5-flash-preview-native-audio-09-2025" \
--set-env-vars="GOOGLE_CLOUD_PROJECT=${PROJECT_ID}" \
--set-env-vars="GOOGLE_CLOUD_LOCATION=${REGION}"
Sau khi lệnh hoàn tất, bạn sẽ thấy một URL dịch vụ (ví dụ: https://mission-bravo-...run.app). Ứng dụng này hiện đang hoạt động trên đám mây.
👉 Truy cập vào trang Google Cloud Run rồi chọn dịch vụ biometric-scout trong danh sách. 
Tìm URL công khai xuất hiện ở đầu trang thông tin chi tiết Dịch vụ. 
Kiểm tra hệ thống lần cuối (Kiểm thử toàn diện)
👉 Giờ đây, bạn sẽ tương tác với hệ thống trực tiếp.
- Lấy URL: Sao chép URL dịch vụ từ đầu ra của lệnh triển khai cuối cùng (URL này phải kết thúc bằng
run.app). - Mở Cockpit: Dán URL vào trình duyệt web.
- Bắt đầu liên hệ: Khi giao diện tải, hãy nhớ cho phép giao diện này truy cập vào màn hình và micrô của bạn.
- Yêu cầu dữ liệu: Khi được giao nhiệm vụ lái xe, hãy yêu cầu bắt đầu lắp ráp. Ví dụ: "Bắt đầu lắp ráp"

Giờ đây, bạn đang tương tác với một hệ thống nhiều tác nhân được triển khai đầy đủ và chạy hoàn toàn trên Google Cloud.
Hệ thống Đa tác nhân khoá vòng ngăn chặn cuối cùng vào vị trí, và bức xạ thất thường sẽ ổn định thành một tiếng vo ve đều đặn.
"Warp Drive: STABILIZED. Rescue Craft: ENGINES IGNITED."

Trên màn hình, con tàu của người ngoài hành tinh lao lên, suýt chút nữa thì bị bề mặt đang sụp đổ của Ozymandias nuốt chửng khi bầu khí quyển sụp đổ. Nó đi vào quỹ đạo an toàn cùng với tàu của bạn, và hệ thống liên lạc tràn ngập giọng nói của những người sống sót – họ vẫn còn sống nhưng đang rất hoảng loạn. Khi quá trình giải cứu hoàn tất và đường về nhà của bạn thông thoáng, đường liên kết từ xa sẽ bị cắt.
Nhờ bạn, những người sống sót đã được giải cứu.
Nếu bạn đã tham gia Cấp độ 0, đừng quên kiểm tra tiến trình của bạn trong nhiệm vụ trên đường về nhà!
