1. ภาพรวม
นักเดินทางในยุคปัจจุบันคาดหวังประสบการณ์การสนทนา แทนที่จะต้องไปยังตัวกรอง UI ที่ซับซ้อน ผู้ใช้ต้องการถามว่า "ฉันพาสุนัขขึ้นรถบัสเวลา 9 โมงเช้าไปบอสตันได้ไหม" ซึ่งต้องใช้เอเจนต์ที่สามารถให้เหตุผลใน Unstructured Data (นโยบาย PDF) และ Structured Data (กำหนดการ SQL)
ในแล็บนี้ เราจะสร้าง Cymbal Transit Agent โดยใช้สิ่งต่อไปนี้
- LangChain4j: เฟรมเวิร์ก Java ชั้นนำสำหรับการจัดการ AI เป็นกลุ่ม
- AlloyDB: ฐานข้อมูลที่มีประสิทธิภาพสูงและเข้ากันได้กับ PostgreSQL
- MCP Toolbox Java SDK: วิธีมาตรฐานในการเชื่อมต่อเอเจนต์ Java กับเครื่องมือและแหล่งข้อมูลภายนอก
สิ่งที่คุณจะสร้าง
Cymbal Bus Agent ซึ่งเป็นแอปพลิเคชัน Java Spring Boot ที่ประกอบด้วย
- ฐานข้อมูล AlloyDB และ MCP Toolbox Java SDK สำหรับการจัดระเบียบเครื่องมือด้วยเอเจนต์
- Cloud Run สำหรับการติดตั้งใช้งานกล่องเครื่องมือและแอปพลิเคชัน (การติดตั้งใช้งานเอเจนต์)
- ไลบรารี LangChain4J สำหรับเฟรมเวิร์กเอเจนต์และ LLM ในแอปพลิเคชัน Spring Boot ด้วย Java 17
สิ่งที่คุณจะได้เรียนรู้
- วิธีใช้ LangChain4J เพื่อสร้างเอเจนต์และเอเจนต์ย่อยเฉพาะทางที่ประสานงานโดยใช้ MCP Toolbox สำหรับ Java SDK ของฐานข้อมูล
- วิธีตั้งค่าและใช้ AlloyDB สำหรับข้อมูลและ AI
- วิธีใช้กล่องเครื่องมือ MCP เพื่อเชื่อมต่อตัวแทนกับเครื่องมือข้อมูล AlloyDB
- วิธีทำให้โซลูชันใช้งานได้โดยใช้ Cloud Run หรือเรียกใช้ในเครื่อง
สถาปัตยกรรม
- AlloyDB สำหรับ PostgreSQL: ทำหน้าที่เป็นฐานข้อมูลการดำเนินงานที่มีประสิทธิภาพสูงซึ่งจัดเก็บเส้นทาง นโยบาย และบันทึกการจอง ซึ่งขับเคลื่อนการค้นหาและการดึงข้อมูลเวกเตอร์
- MCP Toolbox สำหรับ Java SDK ของฐานข้อมูล: ทำหน้าที่เป็น "Orchestration Maestro" โดยแสดงข้อมูล AlloyDB เป็นเครื่องมือที่เรียกใช้งานได้ซึ่งเอเจนต์สามารถเรียกใช้ได้
MCP Toolbox Java SDK ช่วยให้คุณจัดการ Agent ด้วยเครื่องมือฐานข้อมูลได้อย่างง่ายดายสำหรับแอปพลิเคชันระดับองค์กร
- LangChain4J: ไลบรารี Java แบบโอเพนซอร์สที่ช่วยให้การผสานรวมโมเดลภาษาขนาดใหญ่ (LLM) เข้ากับแอปพลิเคชัน Java เป็นเรื่องง่าย โดยมีเครื่องมือและนามธรรมสำหรับการสร้างแอปพลิเคชันที่ทำงานด้วยระบบ AI ซึ่งรวมถึงแชทบ็อต เอเจนต์ และระบบ Retrieval-Augmented Generation (RAG)
- Cloud Run: แพลตฟอร์ม SERVERLESS ที่มีการจัดการครบวงจรซึ่งช่วยให้คุณสร้างและทำให้แอปหรือเว็บไซต์ใช้งานได้ในทุกภาษา ทุกไลบรารี และทุกไบนารีได้อย่างรวดเร็วและง่ายดาย คุณสามารถเขียนโค้ดโดยใช้ภาษา เฟรมเวิร์ก และไลบรารีที่ชื่นชอบ จัดแพ็กเกจเป็นคอนเทนเนอร์ เรียกใช้ "gcloud run deploy" แล้วแอปของคุณจะพร้อมใช้งาน โดยมีทุกอย่างที่จำเป็นต่อการเรียกใช้ในเวอร์ชันที่ใช้งานจริง คุณจะสร้างคอนเทนเนอร์หรือไม่ก็ได้ หากใช้ Go, Node.js, Python, Java, .NET Core หรือ Ruby คุณสามารถใช้ตัวเลือกการติดตั้งใช้งานตามแหล่งที่มาซึ่งจะสร้างคอนเทนเนอร์ให้คุณโดยใช้แนวทางปฏิบัติแนะนำสำหรับภาษาที่คุณใช้
ข้อกำหนด
2. ก่อนเริ่มต้น
สร้างโปรเจ็กต์
- ในคอนโซล Google Cloud ให้เลือกหรือสร้างโปรเจ็กต์ Google Cloud ในหน้าตัวเลือกโปรเจ็กต์
- ตรวจสอบว่าได้เปิดใช้การเรียกเก็บเงินสำหรับโปรเจ็กต์ Cloud แล้ว ดูวิธีตรวจสอบว่าโปรเจ็กต์เปิดใช้การเรียกเก็บเงินหรือไม่
- คุณจะใช้ Cloud Shell ซึ่งเป็นสภาพแวดล้อมบรรทัดคำสั่งที่ทำงานใน Google Cloud คลิกเปิดใช้งาน Cloud Shell ที่ด้านบนของคอนโซล Google Cloud

- เมื่อเชื่อมต่อกับ Cloud Shell แล้ว ให้ตรวจสอบว่าคุณได้รับการตรวจสอบสิทธิ์แล้วและตั้งค่าโปรเจ็กต์เป็นรหัสโปรเจ็กต์โดยใช้คำสั่งต่อไปนี้
gcloud auth list
- เรียกใช้คำสั่งต่อไปนี้ใน Cloud Shell เพื่อยืนยันว่าคำสั่ง gcloud รู้จักโปรเจ็กต์ของคุณ
gcloud config list project
- หากไม่ได้ตั้งค่าโปรเจ็กต์ ให้ใช้คำสั่งต่อไปนี้เพื่อตั้งค่า
gcloud config set project <YOUR_PROJECT_ID>
- เปิดใช้ API ที่จำเป็น: ทำตามลิงก์และเปิดใช้ API
หรือจะใช้คำสั่ง gcloud สำหรับการดำเนินการนี้ก็ได้ โปรดดูคำสั่งและการใช้งาน gcloud ในเอกสารประกอบ
ข้อควรระวังและการแก้ปัญหา
กลุ่มอาการ"โปรเจ็กต์ผี" | คุณเรียกใช้ |
แผงกั้น การเรียกเก็บเงิน | คุณเปิดใช้โปรเจ็กต์แล้ว แต่ลืมบัญชีสำหรับการเรียกเก็บเงิน AlloyDB เป็นเครื่องมือที่มีประสิทธิภาพสูง จึงจะไม่เริ่มทำงานหาก "ถังน้ำมัน" (การเรียกเก็บเงิน) ว่างเปล่า |
ความล่าช้าการเผยแพร่ API | คุณคลิก "เปิดใช้ API" แล้ว แต่บรรทัดคำสั่งยังคงแสดง |
คำถามเกี่ยวกับโควต้า | หากใช้บัญชีทดลองใช้ใหม่ คุณอาจถึงโควต้าระดับภูมิภาคสำหรับอินสแตนซ์ AlloyDB หาก |
ตัวแทนบริการ"ซ่อน" | บางครั้ง AlloyDB Service Agent จะไม่ได้รับบทบาท |
3. การตั้งค่าฐานข้อมูล
AlloyDB สำหรับ PostgreSQL เป็นหัวใจสำคัญของแอปพลิเคชันของเรา เราใช้ประโยชน์จากความสามารถด้านเวกเตอร์ที่มีประสิทธิภาพและผสานรวมเครื่องมือแบบคอลัมน์เพื่อสร้างการฝังสำหรับบันทึก SCM กว่า 50,000 รายการ ซึ่งช่วยให้วิเคราะห์เวกเตอร์แบบเกือบเรียลไทม์ได้ ทำให้ตัวแทนของเราสามารถระบุความผิดปกติของสินค้าคงคลังหรือความเสี่ยงด้านลอจิสติกส์ในชุดข้อมูลขนาดใหญ่ได้ในเวลาไม่กี่มิลลิวินาที
ในแล็บนี้ เราจะใช้ AlloyDB เป็นฐานข้อมูลสำหรับข้อมูลทดสอบ โดยจะใช้คลัสเตอร์เพื่อเก็บทรัพยากรทั้งหมด เช่น ฐานข้อมูลและบันทึก แต่ละคลัสเตอร์มีอินสแตนซ์หลักที่ให้จุดเข้าถึงข้อมูล ตารางจะเก็บข้อมูลจริง
มาสร้างคลัสเตอร์ อินสแตนซ์ และตาราง AlloyDB ที่จะโหลดชุดข้อมูลทดสอบกัน
- คลิกปุ่มหรือคัดลอกลิงก์ด้านล่างไปยังเบราว์เซอร์ที่คุณเข้าสู่ระบบผู้ใช้ Google Cloud Console
หรือคุณสามารถไปที่เทอร์มินัล Cloud Shell จากโปรเจ็กต์ที่คุณแลกบัญชีสำหรับการเรียกเก็บเงิน และโคลนที่เก็บ GitHub แล้วไปยังโปรเจ็กต์โดยใช้คำสั่งด้านล่าง
git clone https://github.com/AbiramiSukumaran/easy-alloydb-setup
cd easy-alloydb-setup
- เมื่อทำขั้นตอนนี้เสร็จแล้ว ระบบจะโคลนที่เก็บไปยังโปรแกรมแก้ไข Cloud Shell ในเครื่อง และคุณจะเรียกใช้คำสั่งด้านล่างจากโฟลเดอร์โปรเจ็กต์ได้ (โปรดตรวจสอบว่าคุณอยู่ในไดเรกทอรีโปรเจ็กต์)
sh run.sh
- ตอนนี้ให้ใช้ UI (คลิกลิงก์ในเทอร์มินัลหรือคลิกลิงก์ "ดูตัวอย่างบนเว็บ" ในเทอร์มินัล
- ป้อนรายละเอียดสำหรับรหัสโปรเจ็กต์ ชื่อคลัสเตอร์ และชื่ออินสแตนซ์เพื่อเริ่มต้นใช้งาน
- ไปหากาแฟดื่มระหว่างที่บันทึกเลื่อนลงมาเรื่อยๆ และคุณสามารถอ่านเกี่ยวกับวิธีที่ระบบดำเนินการนี้เบื้องหลังได้ที่นี่
ข้อควรระวังและการแก้ปัญหา
ปัญหาเรื่อง "ความอดทน" | คลัสเตอร์ฐานข้อมูลเป็นโครงสร้างพื้นฐานที่มีขนาดใหญ่ หากรีเฟรชหน้าเว็บหรือปิดเซสชัน Cloud Shell เนื่องจาก "ดูเหมือนว่าค้างอยู่" คุณอาจมีอินสแตนซ์ "ผี" ที่มีการจัดสรรบางส่วนและลบไม่ได้หากไม่มีการแทรกแซงด้วยตนเอง |
ภูมิภาคไม่ตรงกัน | หากเปิดใช้ API ใน |
คลัสเตอร์ซอมบี้ | หากก่อนหน้านี้คุณใช้ชื่อเดียวกันสำหรับคลัสเตอร์และไม่ได้ลบคลัสเตอร์ออก สคริปต์อาจแจ้งว่ามีชื่อคลัสเตอร์อยู่แล้ว ชื่อคลัสเตอร์ต้องไม่ซ้ำกันภายในโปรเจ็กต์ |
การหมดเวลาของ Cloud Shell | หากคุณพักดื่มกาแฟเป็นเวลา 30 นาที Cloud Shell อาจเข้าสู่โหมดสลีปและยกเลิกการเชื่อมต่อกระบวนการ |
4. การจัดสรรสคีมา
เมื่อคลัสเตอร์และอินสแตนซ์ AlloyDB ทำงานแล้ว ให้ไปที่โปรแกรมแก้ไข SQL ของ AlloyDB Studio เพื่อเปิดใช้ส่วนขยาย AI และจัดสรรสคีมา

คุณอาจต้องรอให้อินสแตนซ์สร้างเสร็จเรียบร้อย เมื่อพร้อมแล้ว ให้ลงชื่อเข้าใช้ AlloyDB โดยใช้ข้อมูลเข้าสู่ระบบที่คุณสร้างขึ้นเมื่อสร้างคลัสเตอร์ ใช้ข้อมูลต่อไปนี้เพื่อตรวจสอบสิทธิ์ใน PostgreSQL
- ชื่อผู้ใช้ : "
postgres" - ฐานข้อมูล : "
postgres" - รหัสผ่าน : "
alloydb" (หรือรหัสผ่านที่คุณตั้งค่าไว้ตอนสร้าง)
เมื่อตรวจสอบสิทธิ์ใน AlloyDB Studio สำเร็จแล้ว ให้ป้อนคำสั่ง SQL ในเอดิเตอร์ คุณเพิ่มหน้าต่างเอดิเตอร์หลายหน้าต่างได้โดยใช้เครื่องหมายบวกทางด้านขวาของหน้าต่างสุดท้าย

คุณจะป้อนคำสั่งสำหรับ AlloyDB ในหน้าต่างเอดิเตอร์โดยใช้ตัวเลือกเรียกใช้ จัดรูปแบบ และล้างตามที่จำเป็น
เปิดใช้ส่วนขยาย
ในการสร้างแอปนี้ เราจะใช้ส่วนขยาย pgvector และ google_ml_integration ส่วนขยาย pgvector ช่วยให้คุณจัดเก็บและค้นหาการฝังเวกเตอร์ได้ ส่วนขยาย google_ml_integration มีฟังก์ชันที่คุณใช้เพื่อเข้าถึงปลายทางการคาดการณ์ของ Vertex AI เพื่อรับการคาดการณ์ใน SQL เปิดใช้ส่วนขยายเหล่านี้โดยเรียกใช้ DDL ต่อไปนี้
CREATE EXTENSION IF NOT EXISTS google_ml_integration CASCADE;
CREATE EXTENSION IF NOT EXISTS vector;
ให้สิทธิ์
เรียกใช้คำสั่งด้านล่างเพื่อให้สิทธิ์ดำเนินการในฟังก์ชัน "embedding"
GRANT EXECUTE ON FUNCTION embedding TO postgres;
มอบบทบาทผู้ใช้ Vertex AI ให้กับบัญชีบริการ AlloyDB
จากคอนโซล Google Cloud IAM ให้สิทธิ์เข้าถึงบทบาท "ผู้ใช้ Vertex AI" แก่บัญชีบริการ AlloyDB (ซึ่งมีลักษณะดังนี้ service-<<PROJECT_NUMBER >>@gcp-sa-alloydb.iam.gserviceaccount.com) PROJECT_NUMBER จะมีหมายเลขโปรเจ็กต์ของคุณ
หรือคุณจะเรียกใช้คำสั่งด้านล่างจากเทอร์มินัลของ Cloud Shell ก็ได้
PROJECT_ID=$(gcloud config get-value project)
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:service-$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)")@gcp-sa-alloydb.iam.gserviceaccount.com" \
--role="roles/aiplatform.user"
สร้างตาราง
คุณสร้างตารางได้โดยใช้คำสั่ง DDL ด้านล่างใน AlloyDB Studio
DROP TABLE IF EXISTS transit_policies;
DROP TABLE IF EXISTS bus_schedules;
DROP TABLE IF EXISTS bookings;
-- Table 1: Transit Policies (Unstructured Data for RAG)
CREATE TABLE transit_policies (
policy_id SERIAL PRIMARY KEY,
category VARCHAR(50),
policy_text TEXT,
policy_embedding vector(768)
);
-- Table 2: Intercity Bus Schedules (Structured Data)
CREATE TABLE bus_schedules (
trip_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
origin_city VARCHAR(100),
destination_city VARCHAR(100),
departure_time TIMESTAMP,
arrival_time TIMESTAMP,
available_seats INT DEFAULT 50,
ticket_price DECIMAL(6,2)
);
-- Table 3: Booking Ledger (Transactional Action Data)
CREATE TABLE bookings (
booking_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
trip_id UUID REFERENCES bus_schedules(trip_id),
passenger_id VARCHAR(100),
status VARCHAR(20) DEFAULT 'CONFIRMED',
booking_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
คอลัมน์ policy_embedding จะอนุญาตให้จัดเก็บค่าเวกเตอร์ของช่องข้อความบางช่อง
การนำเข้าข้อมูล
เรียกใช้ชุดคำสั่ง SQL ด้านล่างเพื่อแทรกระเบียนจำนวนมากในตารางที่เกี่ยวข้อง
- แทรกนโยบายที่ไม่มีโครงสร้างและสร้างการฝังข้อความจริงใน AlloyDB โดยตรง
-- 1. Insert Unstructured Policies and GENERATE REAL EMBEDDINGS natively in AlloyDB
INSERT INTO transit_policies (category, policy_text, policy_embedding)
VALUES
('Pets', 'Service animals are always welcome. Small pets (under 25 lbs) are allowed in secure carriers for a $25 fee. Large dogs are not permitted on standard coaches.', embedding('text-embedding-005', 'Service animals are always welcome. Small pets (under 25 lbs) are allowed in secure carriers for a $25 fee. Large dogs are not permitted on standard coaches.')),
('Luggage', 'Each passenger is allowed one carry-on (up to 15 lbs) and two stowed bags (up to 50 lbs each) free of charge. Additional bags cost $15 each.', embedding('text-embedding-005', 'Each passenger is allowed one carry-on (up to 15 lbs) and two stowed bags (up to 50 lbs each) free of charge. Additional bags cost $15 each.')),
('Refunds', 'Tickets are fully refundable up to 24 hours before departure. Within 24 hours, tickets can be exchanged for travel credit only.', embedding('text-embedding-005', 'Tickets are fully refundable up to 24 hours before departure. Within 24 hours, tickets can be exchanged for travel credit only.'));
- สร้างตารางเวลาที่สมจริงกว่า 200 รายการเป็นเวลา 7 วันโดยใช้ generate_series
-- 2. Generate 200+ Realistic Schedules for the Next 7 Days using generate_series
INSERT INTO bus_schedules (origin_city, destination_city, departure_time, arrival_time, ticket_price, available_seats)
SELECT
origin,
destination,
-- Generate departures every 4 hours starting from tomorrow
(CURRENT_DATE + 1) + (interval '4 hours' * seq) AS dep_time,
(CURRENT_DATE + 1) + (interval '4 hours' * seq) + interval '4.5 hours' AS arr_time,
ROUND((RANDOM() * 30 + 25)::numeric, 2) AS price, -- Random price between $25 and $55
FLOOR(RANDOM() * 50 + 1) AS seats -- Random seats between 1 and 50
FROM
(VALUES
('New York', 'Boston'), ('Boston', 'New York'),
('Philadelphia', 'Washington DC'), ('Washington DC', 'Philadelphia'),
('Seattle', 'Portland'), ('Portland', 'Seattle')
) AS routes(origin, destination)
CROSS JOIN generate_series(1, 40) AS seq; -- 6 routes * 40 time slots = 240 distinct trips ingested!
สร้างการฝัง
ระบบจะครอบคลุมการฝังในคำสั่งแทรกในตาราง transit_policies โดยอัตโนมัติโดยใช้ฟังก์ชัน "embedding('text-embedding-005', '<<policytext>>')"
ข้อควรระวังและการแก้ปัญหา
วงจร "ลืมรหัสผ่าน" | หากคุณใช้การตั้งค่า "คลิกเดียว" และจำรหัสผ่านไม่ได้ ให้ไปที่หน้าข้อมูลพื้นฐานของอินสแตนซ์ในคอนโซล แล้วคลิก "แก้ไข" เพื่อรีเซ็ตรหัสผ่าน |
ข้อผิดพลาด "ไม่พบส่วนขยาย" | หาก |
ปัญหาการเผยแพร่ IAM | คุณเรียกใช้
|
มิติข้อมูลเวกเตอร์ไม่ตรงกัน | ตั้งค่าคอลัมน์ |
พิมพ์รหัสโปรเจ็กต์ผิด | ใน |
5. การตั้งค่าเครื่องมือและกล่องเครื่องมือ
MCP Toolbox for Databases เป็นเซิร์ฟเวอร์ MCP แบบโอเพนซอร์สสำหรับฐานข้อมูล ซึ่งช่วยให้คุณพัฒนาเครื่องมือได้ง่ายขึ้น เร็วขึ้น และปลอดภัยยิ่งขึ้นด้วยการจัดการความซับซ้อนต่างๆ เช่น การจัดกลุ่มการเชื่อมต่อ การตรวจสอบสิทธิ์ และอื่นๆ กล่องเครื่องมือช่วยคุณสร้างเครื่องมือ Gen AI ที่ช่วยให้ตัวแทนเข้าถึงข้อมูลในฐานข้อมูลได้
เราใช้กล่องเครื่องมือ Model Context Protocol (MCP) สำหรับฐานข้อมูลเป็น "ตัวนำ" โดยทำหน้าที่เป็นมิดเดิลแวร์มาตรฐานระหว่างเอเจนต์กับ AlloyDB การกำหนดtools.yamlการกำหนดค่าfind-bus-schedules and routesจะทำให้กล่องเครื่องมือแสดงการดำเนินการฐานข้อมูลที่ซับซ้อนโดยอัตโนมัติเป็นเครื่องมือที่สะอาดและเรียกใช้งานได้ เช่น query-schedules for specific routes และดำเนินการอัตโนมัติ เช่น book-ticket ซึ่งจะช่วยให้ไม่ต้องใช้การจัดกลุ่มการเชื่อมต่อด้วยตนเองหรือ SQL แบบ Boilerplate ภายในตรรกะของเอเจนต์
การติดตั้งเซิร์ฟเวอร์กล่องเครื่องมือ
จากเทอร์มินัล Cloud Shell ให้สร้างโฟลเดอร์สำหรับบันทึกไฟล์ YAML ของเครื่องมือใหม่และไบนารีของกล่องเครื่องมือ
mkdir cymbal-bus-toolbox
cd cymbal-bus-toolbox
จากภายในโฟลเดอร์ใหม่ ให้เรียกใช้ชุดคำสั่งต่อไปนี้
# see releases page for other versions
export VERSION=0.27.0
curl -L -o toolbox https://storage.googleapis.com/genai-toolbox/v$VERSION/linux/amd64/toolbox
chmod +x toolbox
จากนั้นสร้างไฟล์ tools.yaml ภายในโฟลเดอร์ใหม่นั้นโดยไปที่ Cloud Shell Editor แล้วคัดลอกเนื้อหาของไฟล์ repo นี้ลงในไฟล์ tools.yaml
... (Refer to entire file in the repo)
tools:
find-bus-schedules:
kind: postgres-sql
source: alloydb
description: Find all available bus schedules.
statement: |
SELECT CAST(trip_id AS TEXT) trip_id, departure_time, arrival_time, ticket_price, available_seats , origin_city, destination_city
FROM bus_schedules;
query-schedules:
kind: postgres-sql
source: alloydb
description: Find available bus schedules between an origin and destination city.
parameters:
- name: origin
type: string
description: The departure city name.
- name: destination
type: string
description: The arrival city name.
statement: |
SELECT CAST(trip_id AS TEXT) trip_id, departure_time, arrival_time, ticket_price, available_seats
FROM bus_schedules
WHERE lower(origin_city) = lower($1)
AND lower(destination_city) = lower($2)
AND available_seats > 0
ORDER BY departure_time ASC
LIMIT 5;
book-ticket:
kind: postgres-sql
source: alloydb
description: Books a ticket for a specific trip, decrementing available seats and generating a confirmed booking record.
parameters:
- name: trip_id
type: string
description: The UUID of the trip schedule to book.
- name: passenger_name
type: string
description: Name or ID of the passenger (Bound securely via backend or AuthToken).
authServices:
- name: google_auth
field: sub
statement: |
WITH updated_schedule AS (
UPDATE bus_schedules
SET available_seats = available_seats - 1
WHERE trip_id = CAST($1 AS UUID) AND available_seats > 0
RETURNING trip_id
)
INSERT INTO bookings (trip_id, passenger_id)
SELECT trip_id, $2
FROM updated_schedule
RETURNING CAST(booking_id as TEXT) as booking_id, trip_id, passenger_id, status, booking_time;
search-policies:
kind: postgres-sql
source: alloydb
description: Semantic search for transit policies regarding luggage, pets, refunds, and general rules.
parameters:
- name: search_query
type: string
description: The user's question about transit policies to be embedded and searched.
statement: |
SELECT category, policy_text
FROM transit_policies
ORDER BY policy_embedding <=> CAST(embedding('text-embedding-005', $1) AS vector(768))
LIMIT 2;
หมายเหตุ
- ในการตั้งค่า tools.yaml อย่าลืมใส่ ipType: "private" ในการกำหนดค่าแหล่งที่มาของ AlloyDB
- นอกจากนี้ อย่าลืมใส่ URL ของบริการ MCP Toolbox ในพารามิเตอร์ clientId สำหรับการกำหนดค่า authServices คุณอาจได้รับลิงก์หลังจากที่ติดตั้งใช้งานครั้งแรกเท่านั้น ดังนั้นคุณจะต้องเรียกใช้ขั้นตอนการติดตั้งใช้งาน 2 ครั้งเพื่อให้แน่ใจว่ากรณีการใช้งานเครื่องมือที่ผ่านการตรวจสอบสิทธิ์ทำงานได้
- ตัวเลือกด้านล่างสำหรับการทดสอบกล่องเครื่องมือในเครื่องจะใช้ไม่ได้หากตั้งค่าการเชื่อมต่อ AlloyDB เป็นส่วนตัว คุณต้องตั้งค่าให้เป็นแบบสาธารณะเพื่อทดสอบในเครื่องหรือใช้พร็อกซีสำหรับการเชื่อมต่อ แต่ไม่ต้องกังวล ในกรณีของเรา เราจะทำให้ใช้งานได้ใน Cloud Run โดยตรง แล้วจึงทดสอบ
วิธีทดสอบไฟล์ tools.yaml ในเซิร์ฟเวอร์ภายใน
./toolbox --tools-file "tools.yaml"
หรือจะทดสอบใน UI ก็ได้ โดยทำดังนี้
./toolbox --ui
มาทำให้ใช้งานได้ใน Cloud Run กันเลยดังนี้
การติดตั้งใช้งาน Cloud Run
- ตั้งค่าตัวแปรสภาพแวดล้อม PROJECT_ID
export PROJECT_ID="my-project-id"
- เริ่มต้น gcloud CLI
gcloud init
gcloud config set project $PROJECT_ID
- คุณต้องเปิดใช้ API ต่อไปนี้
gcloud services enable run.googleapis.com \
cloudbuild.googleapis.com \
artifactregistry.googleapis.com \
iam.googleapis.com \
secretmanager.googleapis.com
- สร้างบัญชีบริการแบ็กเอนด์หากยังไม่มี
gcloud iam service-accounts create toolbox-identity
- ให้สิทธิ์ในการใช้ Secret Manager โดยทำดังนี้
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member serviceAccount:toolbox-identity@$PROJECT_ID.iam.gserviceaccount.com \
--role roles/secretmanager.secretAccessor
- ให้สิทธิ์เพิ่มเติมแก่บัญชีบริการที่เฉพาะเจาะจงกับแหล่งที่มาของ AlloyDB (roles/alloydb.client และ roles/serviceusage.serviceUsageConsumer)
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member serviceAccount:toolbox-identity@$PROJECT_ID.iam.gserviceaccount.com \
--role roles/alloydb.client
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member serviceAccount:toolbox-identity@$PROJECT_ID.iam.gserviceaccount.com \
--role roles/serviceusage.serviceUsageConsumer
- อัปโหลด tools.yaml เป็นข้อมูลลับ
gcloud secrets create tools-cymbal-transit --data-file=tools.yaml
- หากมีข้อมูลลับอยู่แล้วและต้องการอัปเดตเวอร์ชันของข้อมูลลับ ให้ดำเนินการดังนี้
gcloud secrets versions add tools-cymbal-transit --data-file=tools.yaml
- ตั้งค่าตัวแปรสภาพแวดล้อมเป็นอิมเมจคอนเทนเนอร์ที่ต้องการใช้สำหรับ Cloud Run โดยทำดังนี้
export IMAGE=us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:latest
- ทำให้ Toolbox ใช้งานได้กับ Cloud Run โดยใช้คำสั่งต่อไปนี้
หากเปิดใช้การเข้าถึงแบบสาธารณะในอินสแตนซ์ AlloyDB ให้ทำตามคำสั่งด้านล่างเพื่อทำให้ใช้งานได้ใน Cloud Run
gcloud run deploy toolbox-cymbal-transit \
--image $IMAGE \
--service-account toolbox-identity \
--region us-central1 \
--set-secrets "/app/tools.yaml=tools-cymbal-transit:latest" \
--args="--tools-file=/app/tools.yaml","--address=0.0.0.0","--port=8080" \
--allow-unauthenticated
หากคุณใช้เครือข่าย VPC ให้ใช้คำสั่งด้านล่าง
gcloud run deploy toolbox-cymbal-transit \
--image $IMAGE \
--service-account toolbox-identity \
--region us-central1 \
--set-secrets "/app/tools.yaml=tools-cymbal-transit:latest" \
--args="--tools-file=/app/tools.yaml","--address=0.0.0.0","--port=8080" \
--network <<YOUR_NETWORK_NAME>> \
--subnet <<YOUR_SUBNET_NAME>> \
--allow-unauthenticated
หมายเหตุ: เมื่อติดตั้งใช้งานแล้ว ให้ไปที่รายการบริการ Cloud Run และตรวจสอบว่าได้เลือก "อนุญาตการเข้าถึงแบบสาธารณะ" ในแท็บความปลอดภัยของบริการนั้นแล้ว
6. การตั้งค่าแอปพลิเคชัน Agent
โคลนที่เก็บนี้ลงในโปรเจ็กต์ของคุณ แล้วมาดูรายละเอียดกัน
หากต้องการโคลน ให้เรียกใช้คำสั่งต่อไปนี้จากเทอร์มินัล Cloud Shell (ในไดเรกทอรีรากหรือจากที่ใดก็ตามที่ต้องการสร้างโปรเจ็กต์นี้)
git clone https://github.com/googleapis/mcp-toolbox-sdk-java
คำสั่งด้านบนจะโคลน mcp-toolbox-sdk-java ทั้งหมด เราต้องการเพียงโปรเจ็กต์ตัวอย่างจากนั้น ดังนั้น ให้ไปที่ไดเรกทอรีรากของโปรเจ็กต์ภายในที่เก็บ
cd mcp-toolbox-sdk-java/demo-applications/cymbal-transit
- ซึ่งจะสร้างโปรเจ็กต์และคุณสามารถยืนยันได้ใน Cloud Shell Editor

- เปิด CymbalTransitController.java แล้วตั้งค่าตัวแปรสภาพแวดล้อม
- GCP_PROJECT_ID
- GCP_REGION
- GEMINI_MODEL_NAME
- MCP_TOOLBOX_URL
หรือ (ใช้เพื่อการพัฒนาเท่านั้น) คุณยังแทนที่ตัวยึดตำแหน่งค่าสำรองที่เกี่ยวข้องได้ด้วย
7. คำแนะนำแบบทีละขั้นเกี่ยวกับโค้ด
CymbalTransitController ทำหน้าที่เป็นจุดแรกเข้าสำหรับบริการ Cloud Run โดยจะจัดการโฟลว์การสนทนาและตรวจสอบว่าตัวแทนมีสิทธิ์เข้าถึงคำขอปัจจุบันของผู้ใช้
การติดตั้งใช้งานเป็นไปตามสถาปัตยกรรมแบบเลเยอร์ที่แยกการจัดระเบียบ AI, การเชื่อมต่อเครื่องมือ และการสื่อสาร MCP ระดับล่าง
1. การกำหนดค่า AI Agent (AgentConfiguration)
คลาสนี้ใช้ @Configuration ของ Spring เพื่อเริ่มต้นคอมโพเนนต์ AI ซึ่งจะเริ่มต้น VertexAiGeminiChatModel และเชื่อมโยงกับอินเทอร์เฟซ Agent ของเรา
@Bean
ChatLanguageModel geminiChatModel() {
return VertexAiGeminiChatModel.builder()
.project(projectId)
.location(region)
.modelName(modelName)
.build();
}
@Bean
TransitAgent transitAgent(ChatLanguageModel chatLanguageModel, TransitAgentTools tools) {
return AiServices.builder(TransitAgent.class)
.chatLanguageModel(chatLanguageModel)
.chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(20))
.tools(tools)
.build();
}
ความสำคัญ: AiServices จะเชื่อมโยงอินเทอร์เฟซกับ LLM MessageWindowChatMemory ช่วยให้ตัวแทนจดจำค่ากำหนดของผู้ใช้ (เช่น กระเป๋าใส่สัตว์เลี้ยงที่กล่าวถึงก่อนหน้านี้) ได้นานถึง 20 ข้อความภายในเซสชันเดียว
2. อินเทอร์เฟซ AI Agent (TransitAgent)
@SystemMessageคำอธิบายประกอบจะกำหนด "ลักษณะตัวตน" และข้อจำกัดในการปฏิบัติงาน โดยเฉพาะกลยุทธ์การกำหนดเส้นทาง
@SystemMessage({
"You are the Cymbal Transit Concierge.",
"CRITICAL INSTRUCTION: On your very first interaction, you MUST use the 'findAllSchedules' tool to fetch and memorize the broad bus routes.",
"ONLY if the user asks a specifically narrowed-down question... should you route to the specific tools like 'querySchedules', 'bookTicket', 'searchPolicies'.",
"Don't show any asterisks while listing results. Keep it formatted and numbered or bulleted."
})
String chat(@MemoryId String sessionId, @UserMessage String userMessage);
ความสำคัญ: กลยุทธ์นี้ช่วยลดเวลาในการตอบสนอง การดึงข้อมูลแบบกว้างก่อนจะช่วยให้ Agent ตอบคำถามทั่วไปเกี่ยวกับการกำหนดเส้นทางได้โดยใช้บริบทภายในของตนเองโดยไม่ต้องเรียกใช้แบ็กเอนด์ที่ซ้ำซ้อน
3. The Toolbox Bridge (TransitAgentTools)
บริการนี้ทำหน้าที่เป็น "มือ" ของ Agent โดยจะแปลการเรียกใช้เครื่องมือ LangChain4j เป็นตรรกะการดำเนินการ
@Tool("Fetches the initial, broad dataset of all available bus schedules and routes.")
public String findAllSchedules() {
return mcpService.findAllSchedules().join();
}
@Tool("Book a ticket for a passenger using a specific trip ID.")
public String bookTicket(String tripId, String passengerName) {
return mcpService.bookTicket(tripId, passengerName).join();
}
การดำเนินการแบบซิงโครนัส: แม้ว่าการเรียก MCP จะเป็นแบบอะซิงโครนัส (ส่งคืน CompletableFuture) แต่ LLM ต้องมีผลลัพธ์ก่อนจึงจะดำเนินการกระบวนการ "ความคิด" ต่อไปได้ เราใช้ .join() เพื่อแสดงผลแบบซิงโครนัสกลับไปยังตัวแทน
4. บริการกล่องเครื่องมือ MCP (McpToolboxService)
นี่คือเลเยอร์การสื่อสารที่ใช้ MCP Toolbox Java SDK เพื่อโต้ตอบกับแบ็กเอนด์ของ AlloyDB
// Identity Management: Fetching OIDC ID Token for Auth
GoogleCredentials credentials = GoogleCredentials.getApplicationDefault();
this.idToken = ((IdTokenProvider) credentials)
.idTokenWithAudience(targetUrl, Collections.emptyList())
.getTokenValue();
// Dynamic Invocation: Executing a tool by name
public CompletableFuture<String> findAllSchedules() {
return mcpClient.invokeTool("find-bus-schedules", Collections.emptyMap()).thenApply(result -> {
return result.content().stream()
.map(content -> content.text())
.collect(Collectors.joining(", ", "[", "]"));
});
}
ความสำคัญ: McpToolboxClient จะจัดการการสื่อสาร JSON-RPC ที่ซับซ้อน bookTicket วิธีนี้แสดงให้เห็นความสามารถของ SDK ในการเชื่อมโยงพารามิเตอร์ที่ซับซ้อนแบบไดนามิกโดยเฉพาะ
5. REST Controller (TransitAgentController)
เราได้ลดความซับซ้อนของปลายทางสุดท้ายอย่างมากเนื่องจาก LangChain4j จะจัดการสถานะและตรรกะ
@PostMapping("/chat")
public ResponseEntity<String> handleUserChat(@RequestBody String userMessage, HttpSession session) {
String sessionId = session.getId();
String agentResponse = transitAgent.chat(sessionId, userMessage);
return ResponseEntity.ok(agentResponse);
}
ความสำคัญ: การแมป HttpSession รหัสกับ @MemoryId ช่วยให้มั่นใจได้ว่าผู้ใช้แต่ละรายจะไม่สับสนกับแผนการเดินทางของผู้อื่น ในขณะเดียวกันก็ช่วยให้โค้ดของตัวควบคุมสะอาดและอ่านง่าย
8. กล่องเครื่องมือ MCP: ความสำคัญและ Java SDK
MCP คืออะไร
โปรดคิดว่า Model Context Protocol (MCP) เป็นตัวแปลสากลสำหรับ AI MCP สร้างขึ้นเพื่อกำหนดมาตรฐานวิธีที่โมเดล AI เชื่อมต่อกับเครื่องมือและชุดข้อมูลภายนอก โดยจะแทนที่สคริปต์การผสานรวมที่กำหนดเองและกระจัดกระจายด้วยโปรโตคอลสากลที่ปลอดภัย ไม่ว่าเอเจนต์ของคุณจะต้องเรียกใช้การค้นหา SQL ที่เกี่ยวข้องกับธุรกรรม ค้นหาเอกสารนโยบายหลายพันฉบับ หรือทริกเกอร์ REST API แต่ MCP ก็มีอินเทอร์เฟซเดียวที่ผสานรวม
กล่องเครื่องมือ MCP สำหรับฐานข้อมูล
ทีมวิศวกรกำลังก้าวข้ามแชทบอทแบบง่ายๆ ไปสร้างระบบเอเจนต์ที่โต้ตอบกับฐานข้อมูลที่สำคัญต่อภารกิจโดยตรง อย่างไรก็ตาม การสร้างเอเจนต์ระดับองค์กรเหล่านี้มักหมายถึงการเผชิญกับอุปสรรคในการผสานรวมโค้ดกาวที่กำหนดเอง, API ที่ไม่เสถียร และตรรกะฐานข้อมูลที่ซับซ้อน
เราจึงขอประกาศเปิดตัว Java SDK สำหรับกล่องเครื่องมือ Model Context Protocol (MCP) สำหรับฐานข้อมูล เพื่อแทนที่คอขวดที่ฮาร์ดโค้ดเหล่านี้ด้วยระนาบควบคุมแบบรวมที่ปลอดภัย การเปิดตัวนี้จะนำการประสานงานของเอเจนต์ที่ปลอดภัยด้วยประเภทข้อมูลระดับเฟิร์สคลาสมาสู่ระบบนิเวศขององค์กรที่ใช้กันอย่างแพร่หลายที่สุดในโลก สถาปัตยกรรมที่สมบูรณ์ของ Java สร้างขึ้นเพื่อตอบสนองความต้องการที่เข้มงวดเหล่านี้โดยเฉพาะ โดยมีระดับการทำงานพร้อมกันสูง ความสมบูรณ์ของธุรกรรมที่เข้มงวด และการจัดการสถานะที่แข็งแกร่ง ซึ่งจำเป็นต่อการขยายขนาดเอเจนต์ AI ที่สำคัญต่อภารกิจอย่างปลอดภัยในสภาพแวดล้อมการผลิต
ทำไมต้องใช้ Java SDK
MCP Toolbox Java SDK ช่วยให้นักพัฒนา Java ทำสิ่งต่อไปนี้ได้
- ใช้เครื่องมือ: เชื่อมต่อกับเซิร์ฟเวอร์ MCP (เช่น MCP Toolbox สำหรับ AlloyDB) และเปลี่ยนความสามารถของเซิร์ฟเวอร์ให้เป็นเมธอด Java ที่ LangChain4j เข้าใจโดยอัตโนมัติ
- ความปลอดภัยของประเภท: ใช้การพิมพ์ที่รัดกุมของ Java สำหรับพารามิเตอร์เครื่องมือ ซึ่งจะช่วยลดข้อผิดพลาด "การหลอน" ในรันไทม์ในการเรียกใช้เครื่องมือ
- ความพร้อมสำหรับองค์กร: ผสานรวมกับ Spring Boot, Quarkus, Micronaut ฯลฯ ได้อย่างง่ายดาย
- เชื่อมต่อได้อย่างง่ายดาย: ไม่ต้องเขียนโค้ด JSON-RPC ที่ซ้ำซ้อน
- การตรวจสอบสิทธิ์มาตรฐาน: การรองรับโทเค็น OIDC ของ Google Cloud โดยเนทีฟช่วยให้มั่นใจได้ว่าการดำเนินการเครื่องมือจะปลอดภัย
และอีกมากมาย
ทรัพยากร Dependency: การกำหนดค่า pom.xml
เพิ่มทรัพยากร Dependency ต่อไปนี้ลงในโปรเจ็กต์ Maven เพื่อรวม MCP Toolbox Java SDK เวอร์ชันล่าสุด
<dependency>
<groupId>com.google.cloud.mcp</groupId>
<artifactId>mcp-toolbox-sdk-java</artifactId>
<version>0.2.0</version>
</dependency>
เพิ่มทรัพยากร Dependency ต่อไปนี้ลงในโปรเจ็กต์ Maven เพื่อรวมอาร์ติแฟกต์ LangChain4j
<!-- LangChain4j Core & Gemini -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>0.35.0</version>
</dependency>
เท่านี้ก็เรียบร้อย!!! เราโคลนโปรเจ็กต์และดูรายละเอียดของเอเจนต์, MCP Toolbox Java SDK และบริบทเรียบร้อยแล้ว
9. การเรียกใช้ในเครื่อง
หากต้องการทดสอบเอเจนต์ในเครื่อง คุณต้องชี้ไปยังเซิร์ฟเวอร์ MCP Toolbox ที่ใช้งานจริง
- ตั้งค่าตัวแปรสภาพแวดล้อม
export GCP_PROJECT_ID="<<YOUR_PROJECT_ID>>"
export GCP_REGION="us-central1"
export GEMINI_MODEL_NAME="gemini-2.5-flash"
export MCP_TOOLBOX_URL="<<YOUR_TOOLBOX_ENDPOINT_URL>>/mcp"
- เรียกใช้ด้วย Maven
mvn compile
mvn spring-boot:run
ซึ่งจะเริ่มตัวแทนในเครื่องและคุณควรจะทดสอบได้
10. มาทำให้ใช้งานได้ใน Cloud Run กัน
ทำให้ใช้งานได้ใน Cloud Run โดยเรียกใช้คำสั่งต่อไปนี้จากเทอร์มินัล Cloud Shell ที่โคลนโปรเจ็กต์และตรวจสอบว่าคุณอยู่ในโฟลเดอร์รูทของโปรเจ็กต์
หากคุณไม่ได้อยู่ในโฟลเดอร์รูทของโปรเจ็กต์ปัจจุบัน ให้เรียกใช้คำสั่งนี้ในเทอร์มินัล Cloud Shell
cd cymbal-transit
หากคุณอยู่ในรูท cymbal-transit อยู่แล้ว ให้เรียกใช้คำสั่งด้านล่างเพื่อทำให้แอปใช้งานได้ใน Cloud Run โดยตรง
gcloud run deploy cymbal-transit --source . --set-env-vars GCP_PROJECT_ID=<<YOUR_PROJECT_ID>>,GCP_REGION=us-central1,GEMINI_MODEL_NAME=gemini-2.5-flash,MCP_TOOLBOX_URL=<<YOUR_MCP_TOOLBOX_URL>> --allow-unauthenticated
แทนที่ค่าสำหรับตัวยึดตำแหน่ง <<YOUR_PROJECT>> and <<YOUR_MCP_TOOLBOX_URL>>
เมื่อคำสั่งเสร็จสิ้นแล้ว ระบบจะแสดง URL ของบริการ คัดลอก
มอบบทบาทไคลเอ็นต์ AlloyDB ให้บัญชีบริการ Cloud Run ซึ่งจะช่วยให้แอปพลิเคชันแบบไม่มีเซิร์ฟเวอร์ของคุณสามารถสร้างอุโมงค์ไปยังฐานข้อมูลได้อย่างปลอดภัย
เรียกใช้คำสั่งนี้ในเทอร์มินัล Cloud Shell
# 1. Get your Project ID and Project Number
PROJECT_ID=$(gcloud config get-value project)
PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)")
# 2. Grant the AlloyDB Client role
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com" \
--role="roles/alloydb.client"
หมายเหตุ: เมื่อติดตั้งใช้งานแล้ว ให้ไปที่รายการบริการ Cloud Run และตรวจสอบว่าได้เลือก "อนุญาตการเข้าถึงแบบสาธารณะ" ในแท็บความปลอดภัยของบริการนั้นแล้ว
ตอนนี้ให้ใช้ URL ของบริการ (ปลายทาง Cloud Run ที่คุณคัดลอกไว้ก่อนหน้านี้) และทดสอบแอป
หมายเหตุ: หากพบปัญหาเกี่ยวกับบริการและมีการระบุว่าหน่วยความจำเป็นสาเหตุ ให้ลองเพิ่มขีดจำกัดหน่วยความจำที่จัดสรรเป็น 1 GiB เพื่อทดสอบ
11. สาธิต
ถามตัวแทนว่า "ฉันต้องเดินทางจากนิวยอร์กไปบอสตันพรุ่งนี้เช้า ฉันจะพาสุนัขโกลเด้นรีทรีฟเวอร์มาได้ไหม" สังเกตว่า Agent ทำสิ่งต่อไปนี้หรือไม่
- ค้นหานโยบายสำหรับสุนัขขนาดใหญ่
- ค้นหากำหนดการที่เฉพาะเจาะจง
- สรุปการเดินทางที่เร็วที่สุดพร้อมรหัสการเดินทาง
- รวมถึงจองตั๋วหากคุณติดตามคำขอการดำเนินการนั้น

12. ล้างข้อมูล
เมื่อแล็บนี้เสร็จแล้ว อย่าลืมลบคลัสเตอร์และอินสแตนซ์ AlloyDB
ซึ่งควรล้างข้อมูลคลัสเตอร์พร้อมกับอินสแตนซ์
13. ขอแสดงความยินดี
คุณสร้างเอเจนต์การขนส่งที่ซับซ้อนซึ่งใช้ Java ได้สำเร็จแล้ว การใช้ประโยชน์จาก LangChain4j สำหรับการจัดระเบียบและ MCP Toolbox Java SDK สำหรับการเชื่อมต่อข้อมูล ทำให้คุณได้สร้างระบบที่สามารถให้เหตุผลในเอเจนต์ เครื่องมือ และแหล่งข้อมูลต่างๆ หากต้องการเริ่มต้นใช้งานการประสานงานแอปพลิเคชันเอเจนต์ด้วย MCP Toolbox for Databases ในฐานข้อมูลหลายรายการ แม้จะอยู่ต่างแพลตฟอร์มกันก็ตาม โปรดเริ่มต้นใช้งาน Java SDK วันนี้ นี่คือบล็อกประกาศการเปิดตัวที่มีข้อมูลโดยละเอียดเกี่ยวกับคลัง หากต้องการสร้างแอปพลิเคชันดังกล่าวด้วยตนเองโดยไม่มีค่าใช้จ่ายตามเวลาที่คุณสะดวกและมีผู้สอนนำ โปรดลงชื่อสมัครใช้ Code Vipassana ที่ https://codevipassana.dev!!!
