1. บทนำ
โค้ดแล็บนี้มุ่งเน้นที่โมเดลภาษาขนาดใหญ่ (LLM) Gemini ซึ่งโฮสต์อยู่ใน Vertex AI บน Google Cloud Vertex AI เป็นแพลตฟอร์มที่ครอบคลุมผลิตภัณฑ์ บริการ และโมเดลแมชชีนเลิร์นนิงทั้งหมดใน Google Cloud
คุณจะใช้ Java เพื่อโต้ตอบกับ Gemini API โดยใช้เฟรมเวิร์ก LangChain4j คุณจะได้ดูตัวอย่างที่ชัดเจนเพื่อใช้ประโยชน์จาก LLM ในการตอบคำถาม การระดมความคิด การแยกเอนทิตีและเนื้อหาที่มีโครงสร้าง การสร้างแบบดึงข้อมูล และการเรียกใช้ฟังก์ชัน
Generative AI คืออะไร
Generative AI หมายถึงการใช้ปัญญาประดิษฐ์ (AI) เพื่อสร้างเนื้อหาใหม่ เช่น ข้อความ รูปภาพ เพลง เสียง และวิดีโอ
Generative AI ขับเคลื่อนโดยโมเดลภาษาขนาดใหญ่ (LLM) ที่ทำงานหลายอย่างพร้อมกันและทำงานที่นอกกรอบได้ เช่น การสรุป การถามตอบ การแยกประเภท และอื่นๆ โมเดลพื้นฐานสามารถปรับให้เข้ากับกรณีการใช้งานเป้าหมายได้โดยใช้ข้อมูลตัวอย่างเพียงเล็กน้อย
Generative AI ทำงานอย่างไร
Generative AI ทำงานโดยใช้โมเดลแมชชีนเลิร์นนิง (ML) เพื่อเรียนรู้รูปแบบและความสัมพันธ์ในชุดข้อมูลเนื้อหาที่มนุษย์สร้างขึ้น จากนั้นจะใช้รูปแบบที่ได้เรียนรู้มาเพื่อสร้างเนื้อหาใหม่
วิธีที่พบบ่อยที่สุดในการฝึกโมเดล Generative AI คือการใช้การเรียนรู้แบบมีผู้ดูแล โดยโมเดลจะได้รับชุดเนื้อหาที่มนุษย์สร้างขึ้นและป้ายกำกับที่เกี่ยวข้อง จากนั้นจะเรียนรู้ที่จะสร้างเนื้อหาที่คล้ายกับเนื้อหาที่มนุษย์สร้างขึ้น
แอปพลิเคชัน Generative AI ที่ใช้กันทั่วไปมีอะไรบ้าง
Generative AI สามารถใช้เพื่อวัตถุประสงค์ต่อไปนี้
- ปรับปรุงการโต้ตอบกับลูกค้าผ่านประสบการณ์การแชทและการค้นหาที่ดียิ่งขึ้น
- สำรวจข้อมูลที่ไม่มีโครงสร้างจำนวนมหาศาลผ่านอินเทอร์เฟซแบบสนทนาและการสรุป
- ช่วยงานที่ต้องทำซ้ำๆ เช่น การตอบกลับคำขอข้อเสนอ การแปลเนื้อหาทางการตลาดในภาษาต่างๆ การตรวจสอบสัญญาของลูกค้าเพื่อให้เป็นไปตามข้อกำหนด และอื่นๆ
Google Cloud มีข้อเสนอ Generative AI อะไรบ้าง
Vertex AI ช่วยให้คุณโต้ตอบ ปรับแต่ง และฝังโมเดลพื้นฐานลงในแอปพลิเคชันได้โดยไม่ต้องมีความเชี่ยวชาญด้าน ML มากนัก คุณสามารถเข้าถึงโมเดลพื้นฐานในโมเดลการ์เดน ปรับแต่งโมเดลผ่าน UI ที่ใช้งานง่ายใน Vertex AI Studio หรือใช้โมเดลใน Notebook ของ Data Science
Vertex AI Search and Conversation ช่วยให้นักพัฒนาแอปสร้างเครื่องมือค้นหาและแชทบอทที่ทำงานด้วย Generative AI ได้อย่างรวดเร็วที่สุด
Gemini สำหรับ Google Cloud ซึ่งขับเคลื่อนโดย Gemini เป็นผู้ทำงานร่วมกันที่ทำงานด้วยระบบ AI ซึ่งพร้อมให้บริการใน Google Cloud และ IDE เพื่อช่วยให้คุณทำงานได้มากขึ้นและเร็วขึ้น Gemini Code Assist จะช่วยเติมโค้ด สร้างโค้ด อธิบายโค้ด และให้คุณแชทกับ Gemini เพื่อถามคำถามทางเทคนิคได้
Gemini คืออะไร
Gemini คือกลุ่มโมเดล Generative AI ที่พัฒนาโดย Google DeepMind ซึ่งออกแบบมาสำหรับกรณีการใช้งานแบบมัลติโมดัล การทำงานได้หลายรูปแบบหมายความว่าโมเดลสามารถประมวลผลและสร้างเนื้อหาประเภทต่างๆ เช่น ข้อความ โค้ด รูปภาพ และเสียง

Gemini มีหลายรูปแบบและขนาด ดังนี้
- Gemini 2.0 Flash: ฟีเจอร์รุ่นใหม่ล่าสุดและความสามารถที่ได้รับการปรับปรุง
- Gemini 2.0 Flash-Lite: โมเดล Gemini 2.0 Flash ที่ได้รับการเพิ่มประสิทธิภาพเพื่อความคุ้มค่าและเวลาในการตอบสนองที่ต่ำ
- Gemini 2.5 Pro: โมเดลการให้เหตุผลที่ล้ำหน้าที่สุดของเราในปัจจุบัน
- Gemini 2.5 Flash: โมเดลการคิดที่มีความสามารถรอบด้าน โดยออกแบบมาเพื่อสร้างสมดุลระหว่างราคาและประสิทธิภาพ
ฟีเจอร์หลัก
- ความสามารถในการประมวลผลข้อมูลหลายรูปแบบ: ความสามารถของ Gemini ในการทำความเข้าใจและจัดการข้อมูลหลายรูปแบบถือเป็นก้าวสำคัญที่เหนือกว่าโมเดลภาษาแบบข้อความเท่านั้นแบบเดิม
- ประสิทธิภาพ: Gemini 2.5 Pro มีประสิทธิภาพเหนือกว่าโมเดลที่ล้ำสมัยที่สุดในปัจจุบันในเกณฑ์เปรียบเทียบหลายรายการ และเป็นโมเดลแรกที่มีประสิทธิภาพเหนือกว่าผู้เชี่ยวชาญที่เป็นมนุษย์ในเกณฑ์เปรียบเทียบ MMLU (Massive Multitask Language Understanding) ที่ท้าทาย
- ความยืดหยุ่น: Gemini มีหลายขนาด จึงปรับให้เข้ากับกรณีการใช้งานต่างๆ ได้ ตั้งแต่การวิจัยขนาดใหญ่ไปจนถึงการติดตั้งใช้งานบนอุปกรณ์เคลื่อนที่
คุณจะโต้ตอบกับ Gemini บน Vertex AI จาก Java ได้อย่างไร
คุณมี 2 ตัวเลือกดังนี้
- ไลบรารี Vertex AI Java API สำหรับ Gemini อย่างเป็นทางการ
- เฟรมเวิร์ก LangChain4j
ใน Codelab นี้ คุณจะได้ใช้เฟรมเวิร์ก LangChain4j
เฟรมเวิร์ก LangChain4j คืออะไร
เฟรมเวิร์ก LangChain4j เป็นไลบรารีโอเพนซอร์สสำหรับการผสานรวม LLM ในแอปพลิเคชัน Java โดยการจัดระเบียบคอมโพเนนต์ต่างๆ เช่น LLM เอง รวมถึงเครื่องมืออื่นๆ เช่น ฐานข้อมูลเวกเตอร์ (สำหรับการค้นหาเชิงความหมาย) ตัวโหลดและตัวแยกเอกสาร (เพื่อวิเคราะห์เอกสารและเรียนรู้จากเอกสาร) ตัวแยกวิเคราะห์เอาต์พุต และอื่นๆ
โปรเจ็กต์นี้ได้รับแรงบันดาลใจจากโปรเจ็กต์ Python ของ LangChain แต่มีเป้าหมายเพื่อให้บริการแก่นักพัฒนา Java

สิ่งที่คุณจะได้เรียนรู้
- วิธีตั้งค่าโปรเจ็กต์ Java เพื่อใช้ Gemini และ LangChain4j
- วิธีส่งพรอมต์แรกไปยัง Gemini โดยใช้โปรแกรม
- วิธีสตรีมคำตอบจาก Gemini
- วิธีสร้างบทสนทนาระหว่างผู้ใช้กับ Gemini
- วิธีใช้ Gemini ในบริบทหลายรูปแบบโดยการส่งทั้งข้อความและรูปภาพ
- วิธีแยกข้อมูลที่มีโครงสร้างที่เป็นประโยชน์จากเนื้อหาที่ไม่มีโครงสร้าง
- วิธีปรับแต่งเทมเพลตพรอมต์
- วิธีทำการจัดประเภทข้อความ เช่น การวิเคราะห์ความรู้สึก
- วิธีแชทกับเอกสารของคุณเอง (Retrieval Augmented Generation)
- วิธีขยายแชทบอทด้วยการเรียกใช้ฟังก์ชัน
- วิธีใช้ Gemma ในเครื่องด้วย Ollama และ TestContainers
สิ่งที่คุณต้องมี
- ความรู้เกี่ยวกับภาษาโปรแกรม Java
- โปรเจ็กต์ Google Cloud
- เบราว์เซอร์ เช่น Chrome หรือ Firefox
2. การตั้งค่าและข้อกำหนด
การตั้งค่าสภาพแวดล้อมแบบเรียนรู้ด้วยตนเอง
- ลงชื่อเข้าใช้ Google Cloud Console แล้วสร้างโปรเจ็กต์ใหม่หรือใช้โปรเจ็กต์ที่มีอยู่ซ้ำ หากยังไม่มีบัญชี Gmail หรือ Google Workspace คุณต้องสร้างบัญชี



- ชื่อโปรเจ็กต์คือชื่อที่แสดงสำหรับผู้เข้าร่วมโปรเจ็กต์นี้ ซึ่งเป็นสตริงอักขระที่ Google APIs ไม่ได้ใช้ คุณอัปเดตได้ทุกเมื่อ
- รหัสโปรเจ็กต์จะไม่ซ้ำกันในโปรเจ็กต์ Google Cloud ทั้งหมดและเปลี่ยนแปลงไม่ได้ (เปลี่ยนไม่ได้หลังจากตั้งค่าแล้ว) Cloud Console จะสร้างสตริงที่ไม่ซ้ำกันโดยอัตโนมัติ ซึ่งโดยปกติแล้วคุณไม่จำเป็นต้องสนใจว่าสตริงนั้นคืออะไร ใน Codelab ส่วนใหญ่ คุณจะต้องอ้างอิงรหัสโปรเจ็กต์ (โดยทั่วไปจะระบุเป็น
PROJECT_ID) หากไม่ชอบรหัสที่สร้างขึ้น คุณอาจสร้างรหัสแบบสุ่มอีกรหัสหนึ่งได้ หรือคุณอาจลองใช้ชื่อของคุณเองและดูว่ามีชื่อนั้นหรือไม่ คุณจะเปลี่ยนแปลงรหัสนี้หลังจากขั้นตอนนี้ไม่ได้ และรหัสจะคงอยู่ตลอดระยะเวลาของโปรเจ็กต์ - โปรดทราบว่ายังมีค่าที่ 3 ซึ่งคือหมายเลขโปรเจ็กต์ที่ API บางตัวใช้ ดูข้อมูลเพิ่มเติมเกี่ยวกับค่าทั้ง 3 นี้ได้ในเอกสารประกอบ
- จากนั้นคุณจะต้องเปิดใช้การเรียกเก็บเงินใน Cloud Console เพื่อใช้ทรัพยากร/API ของ Cloud การทำตาม Codelab นี้จะไม่มีค่าใช้จ่ายมากนัก หรืออาจไม่มีค่าใช้จ่ายเลย หากต้องการปิดทรัพยากรเพื่อหลีกเลี่ยงการเรียกเก็บเงินนอกเหนือจากบทแนะนำนี้ คุณสามารถลบทรัพยากรที่สร้างขึ้นหรือลบโปรเจ็กต์ได้ ผู้ใช้ Google Cloud รายใหม่มีสิทธิ์เข้าร่วมโปรแกรมช่วงทดลองใช้ฟรีมูลค่า$300 USD
เริ่มต้น Cloud Shell
แม้ว่าคุณจะใช้งาน Google Cloud จากแล็ปท็อประยะไกลได้ แต่ใน Codelab นี้คุณจะใช้ Cloud Shell ซึ่งเป็นสภาพแวดล้อมบรรทัดคำสั่งที่ทำงานในระบบคลาวด์
เปิดใช้งาน Cloud Shell
- จาก Cloud Console ให้คลิกเปิดใช้งาน Cloud Shell


หากคุณเริ่มใช้ Cloud Shell เป็นครั้งแรก คุณจะเห็นหน้าจอระดับกลางที่อธิบายว่า Cloud Shell คืออะไร หากเห็นหน้าจอระดับกลาง ให้คลิกต่อไป

การจัดสรรและเชื่อมต่อกับ Cloud Shell จะใช้เวลาไม่นาน

เครื่องเสมือนนี้โหลดเครื่องมือพัฒนาซอฟต์แวร์ทั้งหมดที่จำเป็นไว้แล้ว โดยมีไดเรกทอรีหลักแบบถาวรขนาด 5 GB และทำงานใน Google Cloud ซึ่งช่วยเพิ่มประสิทธิภาพเครือข่ายและการตรวจสอบสิทธิ์ได้อย่างมาก คุณสามารถทำงานส่วนใหญ่หรือทั้งหมดในโค้ดแล็บนี้ได้ด้วยเบราว์เซอร์
เมื่อเชื่อมต่อกับ Cloud Shell แล้ว คุณควรเห็นว่าคุณได้รับการตรวจสอบสิทธิ์และระบบได้ตั้งค่าโปรเจ็กต์เป็นรหัสโปรเจ็กต์ของคุณ
- เรียกใช้คำสั่งต่อไปนี้ใน Cloud Shell เพื่อยืนยันว่าคุณได้รับการตรวจสอบสิทธิ์แล้ว
gcloud auth list
เอาต์พุตของคำสั่ง
Credentialed Accounts
ACTIVE ACCOUNT
* <my_account>@<my_domain.com>
To set the active account, run:
$ gcloud config set account `ACCOUNT`
- เรียกใช้คำสั่งต่อไปนี้ใน Cloud Shell เพื่อยืนยันว่าคำสั่ง gcloud รู้จักโปรเจ็กต์ของคุณ
gcloud config list project
เอาต์พุตของคำสั่ง
[core] project = <PROJECT_ID>
หากไม่ได้ตั้งค่าไว้ คุณตั้งค่าได้ด้วยคำสั่งนี้
gcloud config set project <PROJECT_ID>
เอาต์พุตของคำสั่ง
Updated property [core/project].
3. การเตรียมความพร้อมสภาพแวดล้อมในการพัฒนา
ในโค้ดแล็บนี้ คุณจะได้ใช้เทอร์มินัล Cloud Shell และโปรแกรมแก้ไข Cloud Shell เพื่อพัฒนาโปรแกรม Java
เปิดใช้ Vertex AI API
ในคอนโซล Google Cloud ให้ตรวจสอบว่าชื่อโปรเจ็กต์แสดงอยู่ที่ด้านบนของคอนโซล Google Cloud หากไม่ใช่ ให้คลิกเลือกโปรเจ็กต์เพื่อเปิดเครื่องมือเลือกโปรเจ็กต์ แล้วเลือกโปรเจ็กต์ที่ต้องการ
คุณเปิดใช้ Vertex AI API ได้จากส่วน Vertex AI ของ Google Cloud Console หรือจากเทอร์มินัล Cloud Shell
หากต้องการเปิดใช้จาก Google Cloud Console ให้ไปที่ส่วน Vertex AI ของเมนู Google Cloud Console ก่อน

คลิกเปิดใช้ API ที่แนะนำทั้งหมดในแดชบอร์ด Vertex AI
การดำเนินการนี้จะเปิดใช้ API หลายตัว แต่ API ที่สำคัญที่สุดสำหรับ Codelab คือ aiplatform.googleapis.com
หรือคุณจะเปิดใช้ API นี้จากเทอร์มินัล Cloud Shell ด้วยคำสั่งต่อไปนี้ก็ได้
gcloud services enable aiplatform.googleapis.com
โคลน ที่เก็บใน Github
ในเทอร์มินัล Cloud Shell ให้โคลนที่เก็บสำหรับ Codelab นี้
git clone https://github.com/glaforge/gemini-workshop-for-java-developers.git
หากต้องการตรวจสอบว่าโปรเจ็กต์พร้อมที่จะเรียกใช้แล้วหรือไม่ คุณสามารถลองเรียกใช้โปรแกรม "Hello World" ได้
ตรวจสอบว่าคุณอยู่ในโฟลเดอร์ระดับบนสุด
cd gemini-workshop-for-java-developers/
สร้าง Gradle Wrapper
gradle wrapper
เรียกใช้ด้วย gradlew
./gradlew run
คุณควรเห็นเอาต์พุตต่อไปนี้
.. > Task :app:run Hello World!
เปิดและตั้งค่า Cloud Editor
เปิดโค้ดด้วย Cloud Code Editor จาก Cloud Shell โดยทำดังนี้

ใน Cloud Code Editor ให้เปิดโฟลเดอร์แหล่งข้อมูลของ Codelab โดยเลือก File -> Open Folder แล้วชี้ไปที่โฟลเดอร์แหล่งข้อมูลของ Codelab (เช่น /home/username/gemini-workshop-for-java-developers/)
ตั้งค่าตัวแปรสภาพแวดล้อม
เปิดเทอร์มินัลใหม่ใน Cloud Code Editor โดยเลือก Terminal -> New Terminal ตั้งค่าตัวแปรสภาพแวดล้อม 2 รายการที่จำเป็นสำหรับการเรียกใช้ตัวอย่างโค้ด
- PROJECT_ID - รหัสโปรเจ็กต์ Google Cloud
- LOCATION - ภูมิภาคที่ติดตั้งใช้งานโมเดล Gemini
ส่งออกตัวแปรดังนี้
export PROJECT_ID=$(gcloud config get-value project) export LOCATION=us-central1
4. การเรียกใช้โมเดล Gemini ครั้งแรก
เมื่อตั้งค่าโปรเจ็กต์อย่างถูกต้องแล้ว ก็ถึงเวลาเรียกใช้ Gemini API
ดู QA.java ในไดเรกทอรี app/src/main/java/gemini/workshop
package gemini.workshop;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.model.chat.ChatLanguageModel;
public class QA {
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.build();
System.out.println(model.generate("Why is the sky blue?"));
}
}
ในตัวอย่างแรกนี้ คุณต้องนำเข้าคลาส VertexAiGeminiChatModel ซึ่งใช้การติดตั้งอินเทอร์เฟซ ChatModel
ในเมธอด main คุณจะกำหนดค่าโมเดลภาษาแชทได้โดยใช้เครื่องมือสร้างสำหรับ VertexAiGeminiChatModel และระบุข้อมูลต่อไปนี้
- โปรเจ็กต์
- ตำแหน่ง
- ชื่อโมเดล (
gemini-2.0-flash)
ตอนนี้โมเดลภาษาพร้อมแล้ว คุณสามารถเรียกใช้เมธอด generate() และส่งพรอมต์ คำถาม หรือคำสั่งไปยัง LLM ได้ ในที่นี้ คุณถามคำถามง่ายๆ เกี่ยวกับสิ่งที่ทำให้ท้องฟ้าเป็นสีฟ้า
คุณสามารถเปลี่ยนพรอมต์นี้เพื่อลองใช้คำถามหรืองานอื่นๆ ได้
เรียกใช้ตัวอย่างที่โฟลเดอร์รูทของซอร์สโค้ด
./gradlew run -q -DjavaMainClass=gemini.workshop.QA
คุณควรเห็นเอาต์พุตที่คล้ายกับเอาต์พุตนี้
The sky appears blue because of a phenomenon called Rayleigh scattering. When sunlight enters the atmosphere, it is made up of a mixture of different wavelengths of light, each with a different color. The different wavelengths of light interact with the molecules and particles in the atmosphere in different ways. The shorter wavelengths of light, such as those corresponding to blue and violet light, are more likely to be scattered in all directions by these particles than the longer wavelengths of light, such as those corresponding to red and orange light. This is because the shorter wavelengths of light have a smaller wavelength and are able to bend around the particles more easily. As a result of Rayleigh scattering, the blue light from the sun is scattered in all directions, and it is this scattered blue light that we see when we look up at the sky. The blue light from the sun is not actually scattered in a single direction, so the color of the sky can vary depending on the position of the sun in the sky and the amount of dust and water droplets in the atmosphere.
ยินดีด้วย คุณโทรหา Gemini เป็นครั้งแรกแล้ว
การตอบกลับแบบสตรีม
คุณสังเกตไหมว่าคำตอบมาในครั้งเดียวหลังจากผ่านไปไม่กี่วินาที นอกจากนี้ คุณยังรับคำตอบแบบทีละส่วนได้ด้วย เนื่องจากมีตัวแปรการตอบกลับแบบสตรีมมิง การตอบกลับแบบสตรีม โมเดลจะส่งคืนการตอบกลับทีละส่วนเมื่อพร้อมใช้งาน
ใน Codelab นี้ เราจะใช้การตอบกลับแบบไม่สตรีม แต่มาดูการตอบกลับแบบสตรีมกันเพื่อดูว่าทำได้อย่างไร
ใน StreamQA.java ในไดเรกทอรี app/src/main/java/gemini/workshop คุณจะเห็นการตอบกลับการสตรีมที่ใช้งานอยู่
package gemini.workshop;
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiStreamingChatModel;
import static dev.langchain4j.model.LambdaStreamingResponseHandler.onNext;
public class StreamQA {
public static void main(String[] args) {
StreamingChatLanguageModel model = VertexAiGeminiStreamingChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.maxOutputTokens(4000)
.build();
model.generate("Why is the sky blue?", onNext(System.out::println));
}
}
คราวนี้เราจะนำเข้าตัวแปรคลาสการสตรีม VertexAiGeminiStreamingChatModel ซึ่งใช้การติดตั้งใช้งานอินเทอร์เฟซ StreamingChatLanguageModel นอกจากนี้ คุณยังต้องนำเข้าแบบคงที่ LambdaStreamingResponseHandler.onNext ซึ่งเป็นวิธีการที่สะดวกในการจัดเตรียม StreamingResponseHandler เพื่อสร้างตัวแฮนเดิลการสตรีมด้วยนิพจน์แลมบ์ดาของ Java
คราวนี้ลายเซ็นของเมธอด generate() จะแตกต่างออกไปเล็กน้อย ประเภทการคืนค่าจะเป็น void แทนที่จะเป็นสตริง นอกจากพรอมต์แล้ว คุณยังต้องส่งตัวแฮนเดิลการตอบกลับแบบสตรีมมิงด้วย ในที่นี้ เราสามารถกำหนดนิพจน์ Lambda ที่คุณส่งไปยังเมธอด onNext() ได้เนื่องจากการนำเข้าแบบคงที่เรากล่าวถึงข้างต้น ระบบจะเรียกใช้นิพจน์ Lambda ทุกครั้งที่มีการตอบกลับใหม่ ส่วนระบบจะเรียกใช้ฟังก์ชันหลังก็ต่อเมื่อเกิดข้อผิดพลาดเท่านั้น
เรียกใช้
./gradlew run -q -DjavaMainClass=gemini.workshop.StreamQA
คุณจะได้รับคำตอบที่คล้ายกับชั้นเรียนก่อนหน้า แต่ครั้งนี้คุณจะเห็นว่าคำตอบจะปรากฏขึ้นทีละส่วนในเชลล์ แทนที่จะรอให้คำตอบทั้งหมดแสดง
การกำหนดค่าเพิ่มเติม
สำหรับการกำหนดค่า เราได้กำหนดเฉพาะโปรเจ็กต์ สถานที่ และชื่อโมเดล แต่ก็มีพารามิเตอร์อื่นๆ ที่คุณระบุสำหรับโมเดลได้
temperature(Float temp)เพื่อกำหนดระดับความคิดสร้างสรรค์ที่คุณต้องการให้คำตอบเป็น (0 คือความคิดสร้างสรรค์ต่ำและมักจะอิงตามข้อเท็จจริงมากกว่า ส่วน 2 คือผลลัพธ์ที่มีความคิดสร้างสรรค์มากขึ้น)topP(Float topP)— เพื่อเลือกคำที่เป็นไปได้ซึ่งมีความน่าจะเป็นรวมกันเท่ากับจำนวนทศนิยมนั้น (ระหว่าง 0 ถึง 1)topK(Integer topK)— เพื่อเลือกคำแบบสุ่มจากจำนวนคำที่เป็นไปได้สูงสุดสำหรับการเติมข้อความ (ตั้งแต่ 1 ถึง 40)maxOutputTokens(Integer max)— เพื่อระบุความยาวสูงสุดของคำตอบที่โมเดลให้ (โดยทั่วไปแล้ว 4 โทเค็นจะแสดงคำประมาณ 3 คำ)maxRetries(Integer retries)- ในกรณีที่คุณใช้เกินโควต้าคำขอต่อครั้ง หรือแพลตฟอร์มพบปัญหาทางเทคนิค คุณสามารถให้โมเดลลองเรียกใช้ซ้ำ 3 ครั้ง
ที่ผ่านมาคุณถามคำถามเดียวกับ Gemini แต่คุณก็สามารถสนทนาแบบหลายรอบได้เช่นกัน ซึ่งคุณจะได้สำรวจในส่วนถัดไป
5. แชทกับ Gemini
ในขั้นตอนก่อนหน้า คุณได้ถามคำถามเดียว ตอนนี้ถึงเวลาที่ผู้ใช้และ LLM จะได้สนทนากันจริงๆ แล้ว คำถามและคำตอบแต่ละข้อสามารถต่อยอดจากคำถามและคำตอบก่อนหน้าเพื่อสร้างการสนทนาที่แท้จริง
ดู Conversation.java ในโฟลเดอร์ app/src/main/java/gemini/workshop
package gemini.workshop;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.service.AiServices;
import java.util.List;
public class Conversation {
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.build();
MessageWindowChatMemory chatMemory = MessageWindowChatMemory.builder()
.maxMessages(20)
.build();
interface ConversationService {
String chat(String message);
}
ConversationService conversation =
AiServices.builder(ConversationService.class)
.chatLanguageModel(model)
.chatMemory(chatMemory)
.build();
List.of(
"Hello!",
"What is the country where the Eiffel tower is situated?",
"How many inhabitants are there in that country?"
).forEach( message -> {
System.out.println("\nUser: " + message);
System.out.println("Gemini: " + conversation.chat(message));
});
}
}
การนำเข้าใหม่ที่น่าสนใจ 2 รายการในชั้นเรียนนี้
MessageWindowChatMemoryซึ่งเป็นคลาสที่จะช่วยจัดการลักษณะการสนทนาแบบหลายรอบ และเก็บคำถามและคำตอบก่อนหน้าไว้ในหน่วยความจำภายในAiServices- คลาสการแยกข้อมูลระดับสูงที่จะเชื่อมโยงโมเดลแชทและหน่วยความจำแชทเข้าด้วยกัน
ในวิธีหลัก คุณจะต้องตั้งค่าโมเดล หน่วยความจำแชท และบริการ AI ระบบจะกำหนดค่าโมเดลตามปกติด้วยข้อมูลโปรเจ็กต์ สถานที่ และชื่อโมเดล
สำหรับหน่วยความจำแชท เราใช้เครื่องมือสร้างของ MessageWindowChatMemory เพื่อสร้างหน่วยความจำที่เก็บข้อความ 20 รายการล่าสุดที่แลกเปลี่ยนกัน ซึ่งเป็นหน้าต่างแบบเลื่อนในการสนทนาที่มีการเก็บบริบทไว้ในเครื่องในไคลเอ็นต์คลาส Java ของเรา
จากนั้นคุณจะสร้าง AI service ที่เชื่อมโยงโมเดลแชทกับหน่วยความจำแชท
โปรดสังเกตว่าบริการ AI ใช้ConversationServiceอินเทอร์เฟซที่กำหนดเองที่เรากำหนดไว้ ซึ่ง LangChain4j จะใช้ และรับStringคำค้นหาและส่งคืนStringการตอบกลับ
ตอนนี้ได้เวลาสนทนากับ Gemini แล้ว ก่อนอื่นจะมีการส่งคำทักทายง่ายๆ จากนั้นจะมีการถามคำถามแรกเกี่ยวกับหอไอเฟลเพื่อดูว่าหอไอเฟลตั้งอยู่ในประเทศใด โปรดสังเกตว่าประโยคสุดท้ายเกี่ยวข้องกับคำตอบของคำถามแรก เนื่องจากคุณสงสัยว่ามีประชากรอาศัยอยู่ในประเทศที่มีหอไอเฟลกี่คน โดยไม่ได้กล่าวถึงประเทศที่ระบุไว้ในคำตอบก่อนหน้าอย่างชัดเจน ซึ่งแสดงให้เห็นว่าระบบจะส่งคำถามและคำตอบที่ผ่านมาพร้อมกับพรอมต์ทุกครั้ง
เรียกใช้ตัวอย่าง
./gradlew run -q -DjavaMainClass=gemini.workshop.Conversation
คุณควรเห็นคำตอบ 3 รายการที่คล้ายกับคำตอบต่อไปนี้
User: Hello! Gemini: Hi there! How can I assist you today? User: What is the country where the Eiffel tower is situated? Gemini: France User: How many inhabitants are there in that country? Gemini: As of 2023, the population of France is estimated to be around 67.8 million.
คุณสามารถถามคำถามแบบเทิร์นเดียวหรือสนทนาแบบหลายเทิร์นกับ Gemini ได้ แต่ที่ผ่านมานั้น คุณป้อนข้อมูลได้เฉพาะข้อความ แล้วรูปภาพล่ะ มาดูรูปภาพในขั้นตอนถัดไปกัน
6. ความสามารถในการประมวลผลข้อมูลหลายรูปแบบด้วย Gemini
Gemini เป็นโมเดลมัลติโมดอล ไม่เพียงแต่รับข้อความเป็นอินพุตเท่านั้น แต่ยังรับรูปภาพหรือแม้แต่วิดีโอเป็นอินพุตได้ด้วย ในส่วนนี้ คุณจะเห็นกรณีการใช้งานสำหรับการผสมข้อความและรูปภาพ
คุณคิดว่า Gemini จะจดจำแมวตัวนี้ได้ไหม

รูปภาพแมวในหิมะจาก Wikipedia
ดู Multimodal.java ในไดเรกทอรี app/src/main/java/gemini/workshop
package gemini.workshop;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.model.output.Response;
import dev.langchain4j.data.message.ImageContent;
import dev.langchain4j.data.message.TextContent;
public class Multimodal {
static final String CAT_IMAGE_URL =
"https://upload.wikimedia.org/wikipedia/" +
"commons/b/b6/Felis_catus-cat_on_snow.jpg";
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.build();
UserMessage userMessage = UserMessage.from(
ImageContent.from(CAT_IMAGE_URL),
TextContent.from("Describe the picture")
);
Response<AiMessage> response = model.generate(userMessage);
System.out.println(response.content().text());
}
}
ในการนำเข้า โปรดสังเกตว่าเราแยกความแตกต่างระหว่างข้อความและเนื้อหาประเภทต่างๆ UserMessage อาจมีทั้งออบเจ็กต์ TextContent และ ImageContent นี่คือการทำงานแบบหลายรูปแบบ ซึ่งเป็นการผสมผสานข้อความและรูปภาพ เราไม่ได้ส่งพรอมต์สตริงธรรมดา แต่จะส่งออบเจ็กต์ที่มีโครงสร้างมากขึ้นซึ่งแสดงถึงข้อความของผู้ใช้ ซึ่งประกอบด้วยเนื้อหารูปภาพและเนื้อหาข้อความ โมเดลจะส่ง Response กลับมาซึ่งมี AiMessage
จากนั้นคุณจะดึง AiMessage จากการตอบกลับผ่าน content() และข้อความจาก text()
เรียกใช้ตัวอย่าง
./gradlew run -q -DjavaMainClass=gemini.workshop.Multimodal
ชื่อรูปภาพอาจช่วยให้คุณทราบเนื้อหาของรูปภาพ แต่เอาต์พุตของ Gemini จะคล้ายกับตัวอย่างต่อไปนี้
A cat with brown fur is walking in the snow. The cat has a white patch of fur on its chest and white paws. The cat is looking at the camera.
การผสมพรอมต์รูปภาพและข้อความจะเปิดโอกาสให้เกิดกรณีการใช้งานที่น่าสนใจ คุณสามารถสร้างแอปพลิเคชันที่ทำสิ่งต่อไปนี้ได้
- จดจำข้อความในรูปภาพ
- ตรวจสอบว่ารูปภาพปลอดภัยที่จะแสดงหรือไม่
- สร้างคำบรรยายแทนรูปภาพ
- ค้นหาผ่านฐานข้อมูลรูปภาพด้วยคำอธิบายข้อความธรรมดา
นอกจากการดึงข้อมูลจากรูปภาพแล้ว คุณยังดึงข้อมูลจากข้อความที่ไม่มีโครงสร้างได้ด้วย ซึ่งคุณจะได้เรียนรู้ในส่วนถัดไป
7. ดึงข้อมูลที่มีโครงสร้างจากข้อความที่ไม่มีโครงสร้าง
มีหลายกรณีที่ข้อมูลสำคัญอยู่ในเอกสารรายงาน ในอีเมล หรือข้อความรูปแบบยาวอื่นๆ ในลักษณะที่ไม่มีโครงสร้าง ในอุดมคติแล้ว คุณคงอยากที่จะดึงรายละเอียดสำคัญที่อยู่ในข้อความที่ไม่มีโครงสร้างออกมาในรูปแบบของออบเจ็กต์ที่มีโครงสร้าง มาดูกันว่าคุณจะทำเช่นนั้นได้อย่างไร
สมมติว่าคุณต้องการดึงชื่อและอายุของบุคคลจากประวัติส่วนตัว ประวัติการทำงาน หรือคำอธิบายของบุคคลนั้น คุณสามารถสั่งให้ LLM แยก JSON จากข้อความที่ไม่มีโครงสร้างได้ด้วยพรอมต์ที่ปรับแต่งอย่างชาญฉลาด (โดยทั่วไปเรียกว่า"การออกแบบพรอมต์")
แต่ในตัวอย่างด้านล่างนี้ เราจะใช้ฟีเจอร์ที่มีประสิทธิภาพของ Gemini ที่เรียกว่าเอาต์พุตที่มีโครงสร้าง หรือบางครั้งก็เป็นการสร้างแบบจำกัด ซึ่งบังคับให้โมเดลแสดงเฉพาะเนื้อหา JSON ที่ถูกต้องตามสคีมา JSON ที่ระบุ แทนที่จะสร้างพรอมต์ที่อธิบายเอาต์พุต JSON
ดู ExtractData.java ใน app/src/main/java/gemini/workshop
package gemini.workshop;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.SystemMessage;
import static dev.langchain4j.model.vertexai.SchemaHelper.fromClass;
public class ExtractData {
record Person(String name, int age) { }
interface PersonExtractor {
@SystemMessage("""
Your role is to extract the name and age
of the person described in the biography.
""")
Person extractPerson(String biography);
}
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.responseMimeType("application/json")
.responseSchema(fromClass(Person.class))
.build();
PersonExtractor extractor = AiServices.create(PersonExtractor.class, model);
String bio = """
Anna is a 23 year old artist based in Brooklyn, New York. She was born and
raised in the suburbs of Chicago, where she developed a love for art at a
young age. She attended the School of the Art Institute of Chicago, where
she studied painting and drawing. After graduating, she moved to New York
City to pursue her art career. Anna's work is inspired by her personal
experiences and observations of the world around her. She often uses bright
colors and bold lines to create vibrant and energetic paintings. Her work
has been exhibited in galleries and museums in New York City and Chicago.
""";
Person person = extractor.extractPerson(bio);
System.out.println(person.name()); // Anna
System.out.println(person.age()); // 23
}
}
มาดูขั้นตอนต่างๆ ในไฟล์นี้กัน
PersonRecord จะกำหนดให้แสดงรายละเอียดที่อธิบายบุคคล (ชื่อและอายุ)PersonExtractorอินเทอร์เฟซกำหนดด้วยเมธอดที่เมื่อระบุสตริงข้อความที่ไม่มีโครงสร้างแล้ว จะแสดงผลอินสแตนซ์PersonextractPerson()มีคำอธิบายประกอบ@SystemMessageที่เชื่อมโยงพรอมต์คำสั่งกับextractPerson()ซึ่งเป็นพรอมต์ที่โมเดลจะใช้เพื่อเป็นแนวทางในการดึงข้อมูล และแสดงรายละเอียดในรูปแบบเอกสาร JSON ที่จะได้รับการแยกวิเคราะห์และยกเลิกการมาร์ชัลเป็นอินสแตนซ์Person
ตอนนี้มาดูเนื้อหาของเมธอด main() กัน
- ระบบจะกำหนดค่าและสร้างอินสแตนซ์ของโมเดลแชท เราใช้ 2 วิธีใหม่ของคลาส Model Builder ได้แก่
responseMimeType()และresponseSchema()คำสั่งแรกจะบอกให้ Gemini สร้าง JSON ที่ถูกต้องในเอาต์พุต วิธีที่ 2 จะกำหนดสคีมาของออบเจ็กต์ JSON ที่ควรแสดงผล นอกจากนี้ เมธอดหลังยังมอบหมายให้เมธอดอำนวยความสะดวกที่สามารถแปลงคลาสหรือบันทึก Java เป็นสคีมา JSON ที่เหมาะสม - ระบบจะสร้างออบเจ็กต์
PersonExtractorด้วยคลาสAiServicesของ LangChain4j - จากนั้นคุณก็โทรหา
Person person = extractor.extractPerson(...)เพื่อดึงรายละเอียดของบุคคลจากข้อความที่ไม่มีโครงสร้าง และรับอินสแตนซ์Personที่มีชื่อและอายุ
เรียกใช้ตัวอย่าง
./gradlew run -q -DjavaMainClass=gemini.workshop.ExtractData
คุณควรเห็นเอาต์พุตต่อไปนี้
Anna 23
ใช่ นี่คือแอนนาและเธออายุ 23 ปี
ด้วยAiServices วิธีนี้ คุณจะทำงานกับออบเจ็กต์ที่มีการพิมพ์อย่างเข้มงวด คุณไม่ได้โต้ตอบกับ LLM โดยตรง แต่คุณกำลังทำงานกับคลาสที่เป็นรูปธรรม เช่น Person บันทึกเพื่อแสดงข้อมูลส่วนบุคคลที่แยกออกมา และคุณมีออบเจ็กต์ PersonExtractor ที่มีเมธอด extractPerson() ซึ่งจะแสดงผลอินสแตนซ์ Person แนวคิดของ LLM จะถูกแยกออกไป และในฐานะนักพัฒนา Java คุณเพียงแค่จัดการคลาสและออบเจ็กต์ปกติเมื่อใช้PersonExtractorอินเทอร์เฟซนี้
8. จัดโครงสร้างพรอมต์ด้วยเทมเพลตพรอมต์
เมื่อคุณโต้ตอบกับ LLM โดยใช้ชุดคำสั่งหรือคำถามทั่วไป จะมีส่วนหนึ่งของพรอมต์ที่ไม่เคยเปลี่ยนแปลง ในขณะที่ส่วนอื่นๆ มีข้อมูล เช่น หากต้องการสร้างสูตรอาหาร คุณอาจใช้พรอมต์อย่าง "คุณเป็นเชฟที่มีความสามารถ โปรดสร้างสูตรอาหารโดยใช้ส่วนผสมต่อไปนี้ ..." จากนั้นคุณจะต่อท้ายส่วนผสมไว้ที่ท้ายข้อความนั้น เทมเพลตพรอมต์มีไว้เพื่อจุดประสงค์นี้ ซึ่งคล้ายกับสตริงที่แทรกในภาษาโปรแกรม เทมเพลตพรอมต์มีตัวยึดตำแหน่งที่คุณแทนที่ด้วยข้อมูลที่ถูกต้องสำหรับการเรียก LLM ที่เฉพาะเจาะจงได้
มาดู TemplatePrompt.java ในไดเรกทอรี app/src/main/java/gemini/workshop กัน
package gemini.workshop;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.model.input.Prompt;
import dev.langchain4j.model.input.PromptTemplate;
import dev.langchain4j.model.output.Response;
import java.util.HashMap;
import java.util.Map;
public class TemplatePrompt {
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.maxOutputTokens(500)
.temperature(1.0f)
.topK(40)
.topP(0.95f)
.maxRetries(3)
.build();
PromptTemplate promptTemplate = PromptTemplate.from("""
You're a friendly chef with a lot of cooking experience.
Create a recipe for a {{dish}} with the following ingredients: \
{{ingredients}}, and give it a name.
"""
);
Map<String, Object> variables = new HashMap<>();
variables.put("dish", "dessert");
variables.put("ingredients", "strawberries, chocolate, and whipped cream");
Prompt prompt = promptTemplate.apply(variables);
Response<AiMessage> response = model.generate(prompt.toUserMessage());
System.out.println(response.content().text());
}
}
เช่นเคย คุณจะกำหนดค่าVertexAiGeminiChatModelโมเดลที่มีความคิดสร้างสรรค์สูงโดยมีอุณหภูมิสูง รวมถึงค่า topP และ topK สูง จากนั้นสร้าง PromptTemplate ด้วยfrom()เมธอดแบบคงที่ โดยส่งสตริงของพรอมต์ และใช้ตัวแปรตัวยึดตำแหน่งที่มีเครื่องหมายปีกกาคู่ {{dish}} และ {{ingredients}}
คุณสร้างพรอมต์สุดท้ายได้โดยเรียกใช้ apply() ซึ่งใช้แผนที่ของคู่คีย์/ค่าที่แสดงชื่อของตัวยึดตำแหน่งและค่าสตริงที่จะใช้แทน
สุดท้าย คุณจะเรียกใช้เมธอด generate() ของโมเดล Gemini ได้โดยการสร้างข้อความผู้ใช้จากพรอมต์นั้นด้วยprompt.toUserMessage()คำสั่ง
เรียกใช้ตัวอย่าง
./gradlew run -q -DjavaMainClass=gemini.workshop.TemplatePrompt
คุณควรเห็นเอาต์พุตที่สร้างขึ้นซึ่งมีลักษณะคล้ายกับเอาต์พุตนี้
**Strawberry Shortcake** Ingredients: * 1 pint strawberries, hulled and sliced * 1/2 cup sugar * 1/4 cup cornstarch * 1/4 cup water * 1 tablespoon lemon juice * 1/2 cup heavy cream, whipped * 1/4 cup confectioners' sugar * 1/4 teaspoon vanilla extract * 6 graham cracker squares, crushed Instructions: 1. In a medium saucepan, combine the strawberries, sugar, cornstarch, water, and lemon juice. Bring to a boil over medium heat, stirring constantly. Reduce heat and simmer for 5 minutes, or until the sauce has thickened. 2. Remove from heat and let cool slightly. 3. In a large bowl, combine the whipped cream, confectioners' sugar, and vanilla extract. Beat until soft peaks form. 4. To assemble the shortcakes, place a graham cracker square on each of 6 dessert plates. Top with a scoop of whipped cream, then a spoonful of strawberry sauce. Repeat layers, ending with a graham cracker square. 5. Serve immediately. **Tips:** * For a more elegant presentation, you can use fresh strawberries instead of sliced strawberries. * If you don't have time to make your own whipped cream, you can use store-bought whipped cream.
คุณสามารถเปลี่ยนค่าของ dish และ ingredients ในแผนที่ รวมถึงปรับอุณหภูมิ topK และ tokP แล้วเรียกใช้โค้ดอีกครั้งได้ ซึ่งจะช่วยให้คุณสังเกตผลของการเปลี่ยนพารามิเตอร์เหล่านี้ใน LLM ได้
เทมเพลตพรอมต์เป็นวิธีที่ดีในการมีคำสั่งที่ใช้ซ้ำได้และกำหนดพารามิเตอร์ได้สำหรับการเรียก LLM คุณสามารถส่งข้อมูลและปรับแต่งพรอมต์สำหรับค่าต่างๆ ที่ผู้ใช้ระบุ
9. การจัดประเภทข้อความด้วย Few-Shot Prompting
LLM ทำได้ดีในการจัดประเภทข้อความเป็นหมวดหมู่ต่างๆ คุณช่วย LLM ในงานนั้นได้โดยระบุตัวอย่างข้อความและหมวดหมู่ที่เกี่ยวข้อง ซึ่งมักเรียกว่าการแจ้งแบบไม่กี่นัด
มาเปิด TextClassification.java ในไดเรกทอรี app/src/main/java/gemini/workshop เพื่อทำการจัดประเภทข้อความประเภทหนึ่งโดยเฉพาะ นั่นคือการวิเคราะห์ความรู้สึก
package gemini.workshop;
import com.google.cloud.vertexai.api.Schema;
import com.google.cloud.vertexai.api.Type;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.SystemMessage;
import java.util.List;
public class TextClassification {
enum Sentiment { POSITIVE, NEUTRAL, NEGATIVE }
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.maxOutputTokens(10)
.maxRetries(3)
.responseSchema(Schema.newBuilder()
.setType(Type.STRING)
.addAllEnum(List.of("POSITIVE", "NEUTRAL", "NEGATIVE"))
.build())
.build();
interface SentimentAnalysis {
@SystemMessage("""
Analyze the sentiment of the text below.
Respond only with one word to describe the sentiment.
""")
Sentiment analyze(String text);
}
MessageWindowChatMemory memory = MessageWindowChatMemory.withMaxMessages(10);
memory.add(UserMessage.from("This is fantastic news!"));
memory.add(AiMessage.from(Sentiment.POSITIVE.name()));
memory.add(UserMessage.from("Pi is roughly equal to 3.14"));
memory.add(AiMessage.from(Sentiment.NEUTRAL.name()));
memory.add(UserMessage.from("I really disliked the pizza. Who would use pineapples as a pizza topping?"));
memory.add(AiMessage.from(Sentiment.NEGATIVE.name()));
SentimentAnalysis sentimentAnalysis =
AiServices.builder(SentimentAnalysis.class)
.chatLanguageModel(model)
.chatMemory(memory)
.build();
System.out.println(sentimentAnalysis.analyze("I love strawberries!"));
}
}
การแจงนับ Sentiment แสดงค่าต่างๆ สำหรับความรู้สึก ได้แก่ เชิงลบ เป็นกลาง หรือเชิงบวก
ในmain()วิธีนี้ คุณจะสร้างโมเดลแชท Gemini ตามปกติ แต่มีจำนวนโทเค็นเอาต์พุตสูงสุดน้อย เนื่องจากคุณต้องการเพียงคำตอบสั้นๆ ซึ่งก็คือข้อความ POSITIVE, NEGATIVE หรือ NEUTRAL และหากต้องการจำกัดโมเดลให้แสดงเฉพาะค่าเหล่านั้น คุณสามารถใช้ประโยชน์จากการรองรับเอาต์พุตที่มีโครงสร้างที่พบในส่วนการแยกข้อมูล จึงมีการใช้เมธอด responseSchema() คราวนี้คุณจะไม่ใช้วิธีที่สะดวกจาก SchemaHelper เพื่ออนุมานคำจำกัดความของสคีมา แต่จะใช้เครื่องมือสร้าง Schema แทน เพื่อทำความเข้าใจว่าคำจำกัดความของสคีมามีลักษณะอย่างไร
เมื่อกำหนดค่าโมเดลแล้ว คุณจะสร้างอินเทอร์เฟซ SentimentAnalysis ที่ AiServices ของ LangChain4j จะใช้ให้คุณโดยใช้ LLM อินเทอร์เฟซนี้มีเมธอดเดียวคือ analyze() โดยจะรับข้อความที่จะวิเคราะห์ในอินพุต และแสดงผลค่า enum ของ Sentiment ดังนั้นคุณจึงจัดการได้เฉพาะออบเจ็กต์ที่มีการพิมพ์อย่างเข้มงวดซึ่งแสดงถึงคลาสของความรู้สึกที่ระบบจดจำได้
จากนั้นเพื่อที่จะให้ "ตัวอย่างแบบไม่กี่ช็อต" เพื่อกระตุ้นให้โมเดลทำงานด้านการจัดประเภท คุณจะต้องสร้างหน่วยความจำแชทเพื่อส่งคู่ข้อความของผู้ใช้และคำตอบของ AI ที่แสดงถึงข้อความและความรู้สึกที่เชื่อมโยงกับข้อความนั้น
มาผูกทุกอย่างเข้าด้วยกันด้วยเมธอด AiServices.builder() โดยส่งอินเทอร์เฟซ SentimentAnalysis โมเดลที่จะใช้ และหน่วยความจำแชทพร้อมตัวอย่างแบบ Few-Shot สุดท้าย ให้เรียกใช้เมธอด analyze() พร้อมข้อความที่จะวิเคราะห์
เรียกใช้ตัวอย่าง
./gradlew run -q -DjavaMainClass=gemini.workshop.TextClassification
คุณควรเห็นคำเดียวต่อไปนี้
POSITIVE
ดูเหมือนว่าการชอบสตรอว์เบอร์รีจะเป็นความรู้สึกเชิงบวกนะ
10. การสร้างเสริมด้วยการดึงข้อมูล
LLM ได้รับการฝึกด้วยข้อความจำนวนมาก อย่างไรก็ตาม ความรู้ของโมเดลจะครอบคลุมเฉพาะข้อมูลที่เห็นในระหว่างการฝึกเท่านั้น หากมีการเผยแพร่ข้อมูลใหม่หลังจากวันที่สิ้นสุดการฝึกโมเดล โมเดลจะไม่มีรายละเอียดดังกล่าว ดังนั้น โมเดลจึงตอบคำถามเกี่ยวกับข้อมูลที่ยังไม่เคยเห็นไม่ได้
ด้วยเหตุนี้ แนวทางต่างๆ เช่น การสร้างข้อความโดยใช้การดึงข้อมูล (RAG) ซึ่งจะกล่าวถึงในส่วนนี้ จึงช่วยให้ข้อมูลเพิ่มเติมที่ LLM อาจต้องทราบเพื่อตอบสนองคำขอของผู้ใช้ รวมถึงตอบกลับด้วยข้อมูลที่อาจเป็นข้อมูลล่าสุดหรือข้อมูลส่วนตัวที่เข้าถึงไม่ได้ในเวลาที่ฝึก
กลับมาที่การสนทนากัน คราวนี้คุณจะถามคำถามเกี่ยวกับเอกสารได้ คุณจะสร้างแชทบอทที่สามารถดึงข้อมูลที่เกี่ยวข้องจากฐานข้อมูลที่มีเอกสารของคุณซึ่งแบ่งออกเป็นชิ้นเล็กๆ ("ก้อน") และโมเดลจะใช้ข้อมูลดังกล่าวเพื่ออ้างอิงคำตอบแทนที่จะอาศัยความรู้ที่มีอยู่ในการฝึกเพียงอย่างเดียว
ใน RAG มี 2 เฟส ดังนี้
- ระยะการนำเข้า - ระบบจะโหลดเอกสารลงในหน่วยความจำ แบ่งออกเป็นส่วนย่อยๆ และคำนวณการฝังเวกเตอร์ (การแสดงเวกเตอร์แบบหลายมิติสูงของส่วนย่อย) แล้วจัดเก็บไว้ในฐานข้อมูลเวกเตอร์ที่สามารถทำการค้นหาเชิงความหมายได้ โดยปกติแล้วระยะการนำเข้านี้จะทำเพียงครั้งเดียวเมื่อต้องเพิ่มเอกสารใหม่ลงในคลังเอกสาร

- ระยะการค้นหา - ตอนนี้ผู้ใช้สามารถถามคำถามเกี่ยวกับเอกสารได้แล้ว ระบบจะแปลงคำถามเป็นเวกเตอร์ด้วยเช่นกัน และนำไปเปรียบเทียบกับเวกเตอร์อื่นๆ ทั้งหมดในฐานข้อมูล เวกเตอร์ที่คล้ายกันมากที่สุดมักจะเกี่ยวข้องกับความหมายและฐานข้อมูลเวกเตอร์จะแสดงเวกเตอร์ดังกล่าว จากนั้น LLM จะได้รับบริบทของการสนทนา ข้อความที่สอดคล้องกับเวกเตอร์ที่ฐานข้อมูลส่งคืน และจะได้รับคำขอให้ยึดคำตอบโดยดูที่ข้อความเหล่านั้น

เตรียมเอกสาร
สำหรับตัวอย่างใหม่นี้ คุณจะถามคำถามเกี่ยวกับรถยนต์รุ่นสมมติจากผู้ผลิตรถยนต์สมมติเช่นกัน นั่นก็คือรถยนต์ Cymbal Starlight แนวคิดคือเอกสารเกี่ยวกับรถยนต์สมมติไม่ควรเป็นส่วนหนึ่งของความรู้ของโมเดล ดังนั้นหาก Gemini ตอบคำถามเกี่ยวกับรถคันนี้ได้อย่างถูกต้อง ก็หมายความว่าแนวทาง RAG ใช้งานได้ นั่นคือสามารถค้นหาในเอกสารของคุณได้
ติดตั้งใช้งานแชทบ็อต
มาดูวิธีสร้างแนวทาง 2 เฟสกัน โดยเริ่มจากการส่งผ่านข้อมูลเอกสารก่อน แล้วจึงเป็นเวลาในการค้นหา (หรือที่เรียกว่า "เฟสการดึงข้อมูล") เมื่อผู้ใช้ถามคำถามเกี่ยวกับเอกสาร
ในตัวอย่างนี้ เราจะใช้ทั้ง 2 เฟสในชั้นเรียนเดียวกัน โดยปกติแล้ว คุณจะมีแอปพลิเคชันหนึ่งที่ดูแลการส่งผ่านข้อมูล และอีกแอปพลิเคชันหนึ่งที่นำเสนออินเทอร์เฟซแชทบอทแก่ผู้ใช้
นอกจากนี้ ในตัวอย่างนี้เราจะใช้ฐานข้อมูลเวกเตอร์ในหน่วยความจำ ในสถานการณ์การใช้งานจริง ขั้นตอนการส่งผ่านข้อมูลและการค้นหาจะแยกออกเป็น 2 แอปพลิเคชันที่แตกต่างกัน และเวกเตอร์จะยังคงอยู่ในฐานข้อมูลแบบสแตนด์อโลน
การส่งผ่านข้อมูลเอกสาร
ขั้นตอนแรกของระยะการนำเข้าเอกสารคือการค้นหาไฟล์ PDF เกี่ยวกับรถยนต์สมมติของเรา และเตรียม PdfParser เพื่ออ่านไฟล์ดังกล่าว
URL url = new URI("https://raw.githubusercontent.com/meteatamel/genai-beyond-basics/main/samples/grounding/vertexai-search/cymbal-starlight-2024.pdf").toURL();
ApachePdfBoxDocumentParser pdfParser = new ApachePdfBoxDocumentParser();
Document document = pdfParser.parse(url.openStream());
คุณจะสร้างอินสแตนซ์ของโมเดลการฝังแทนการสร้างโมเดลภาษาแชทตามปกติก่อน นี่คือโมเดลเฉพาะที่มีบทบาทในการสร้างการแสดงเวกเตอร์ของข้อความ (คำ ประโยค หรือแม้แต่ย่อหน้า) โดยจะแสดงผลเวกเตอร์ของจำนวนทศนิยมแทนที่จะแสดงผลการตอบกลับเป็นข้อความ
VertexAiEmbeddingModel embeddingModel = VertexAiEmbeddingModel.builder()
.endpoint(System.getenv("LOCATION") + "-aiplatform.googleapis.com:443")
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.publisher("google")
.modelName("text-embedding-005")
.maxRetries(3)
.build();
จากนั้นคุณจะต้องมีชั้นเรียน 2-3 ชั้นเรียนเพื่อทำงานร่วมกันในการดำเนินการต่อไปนี้
- โหลดและแยกเอกสาร PDF ออกเป็นส่วนๆ
- สร้างการฝังเวกเตอร์สำหรับก้อนข้อมูลทั้งหมดนี้
InMemoryEmbeddingStore<TextSegment> embeddingStore =
new InMemoryEmbeddingStore<>();
EmbeddingStoreIngestor storeIngestor = EmbeddingStoreIngestor.builder()
.documentSplitter(DocumentSplitters.recursive(500, 100))
.embeddingModel(embeddingModel)
.embeddingStore(embeddingStore)
.build();
storeIngestor.ingest(document);
ระบบจะสร้างอินสแตนซ์ของ InMemoryEmbeddingStore ซึ่งเป็นฐานข้อมูลเวกเตอร์ในหน่วยความจำเพื่อจัดเก็บการฝังเวกเตอร์
เอกสารจะแบ่งออกเป็นส่วนๆ ด้วยDocumentSplitters คลาส โดยจะแยกข้อความในไฟล์ PDF ออกเป็นข้อมูลโค้ดที่มีอักขระ 500 ตัว โดยมีส่วนที่ทับซ้อนกัน 100 ตัว (กับก้อนข้อมูลถัดไป เพื่อหลีกเลี่ยงการตัดคำหรือประโยคออกเป็นชิ้นเล็กๆ)
โปรแกรมเปลี่ยนไฟล์ของร้านค้าจะลิงก์ตัวแยกเอกสาร โมเดลการฝังเพื่อคำนวณเวกเตอร์ และฐานข้อมูลเวกเตอร์ในหน่วยความจำ จากนั้นเมธอด ingest() จะจัดการการส่งผ่านข้อมูล
ตอนนี้ระยะแรกสิ้นสุดลงแล้ว ระบบได้แปลงเอกสารเป็นกลุ่มข้อความพร้อมการฝังเวกเตอร์ที่เชื่อมโยงกัน และจัดเก็บไว้ในฐานข้อมูลเวกเตอร์
การถามคำถาม
ได้เวลาเตรียมพร้อมถามคำถามแล้ว สร้างโมเดลแชทเพื่อเริ่มการสนทนาโดยทำดังนี้
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.maxOutputTokens(1000)
.build();
นอกจากนี้ คุณยังต้องมีคลาส Retriever เพื่อลิงก์ฐานข้อมูลเวกเตอร์ (ในตัวแปร embeddingStore) กับโมเดลการฝัง หน้าที่ของโมเดลคือการค้นหาฐานข้อมูลเวกเตอร์โดยการคำนวณการฝังเวกเตอร์สำหรับคำค้นหาของผู้ใช้ เพื่อค้นหาเวกเตอร์ที่คล้ายกันในฐานข้อมูล
EmbeddingStoreContentRetriever retriever =
new EmbeddingStoreContentRetriever(embeddingStore, embeddingModel);
สร้างอินเทอร์เฟซที่แสดงถึงผู้ช่วยผู้เชี่ยวชาญด้านรถยนต์ ซึ่งเป็นอินเทอร์เฟซที่คลาส AiServices จะใช้เพื่อให้คุณโต้ตอบกับโมเดลได้
interface CarExpert {
Result<String> ask(String question);
}
อินเทอร์เฟซ CarExpert จะแสดงผลการตอบกลับเป็นสตริงที่ห่อหุ้มไว้ในคลาส Result ของ LangChain4j เหตุผลที่ควรใช้ Wrapper นี้ เนื่องจากไม่เพียงแต่จะให้คำตอบแก่คุณเท่านั้น แต่ยังช่วยให้คุณตรวจสอบก้อนข้อมูลจากฐานข้อมูลที่เครื่องมือดึงข้อมูลเนื้อหาส่งคืนมาได้อีกด้วย ด้วยวิธีนี้ คุณจะแสดงแหล่งที่มาของเอกสารที่ใช้ในการอ้างอิงคำตอบสุดท้ายต่อผู้ใช้ได้
ในตอนนี้ คุณสามารถกำหนดค่าบริการ AI ใหม่ได้โดยทำดังนี้
CarExpert expert = AiServices.builder(CarExpert.class)
.chatLanguageModel(model)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.contentRetriever(retriever)
.build();
บริการนี้จะเชื่อมโยงสิ่งต่อไปนี้เข้าด้วยกัน
- โมเดลภาษาแชทที่คุณกำหนดค่าไว้ก่อนหน้านี้
- หน่วยความจำแชทเพื่อติดตามการสนทนา
- ตัวดึงข้อมูลจะเปรียบเทียบคำค้นหาการฝังเวกเตอร์กับเวกเตอร์ในฐานข้อมูล
.retrievalAugmentor(DefaultRetrievalAugmentor.builder()
.contentInjector(DefaultContentInjector.builder()
.promptTemplate(PromptTemplate.from("""
You are an expert in car automotive, and you answer concisely.
Here is the question: {{userMessage}}
Answer using the following information:
{{contents}}
"""))
.build())
.contentRetriever(retriever)
.build())
ในที่สุดคุณก็พร้อมที่จะถามคำถามแล้ว
List.of(
"What is the cargo capacity of Cymbal Starlight?",
"What's the emergency roadside assistance phone number?",
"Are there some special kits available on that car?"
).forEach(query -> {
Result<String> response = expert.ask(query);
System.out.printf("%n=== %s === %n%n %s %n%n", query, response.content());
System.out.println("SOURCE: " + response.sources().getFirst().textSegment().text());
});
ซอร์สโค้ดทั้งหมดอยู่ใน RAG.java ในไดเรกทอรี app/src/main/java/gemini/workshop
เรียกใช้ตัวอย่าง
./gradlew -q run -DjavaMainClass=gemini.workshop.RAG
ในเอาต์พุต คุณควรเห็นคำตอบของคำถาม
=== What is the cargo capacity of Cymbal Starlight? === The Cymbal Starlight 2024 has a cargo capacity of 13.5 cubic feet. SOURCE: Cargo The Cymbal Starlight 2024 has a cargo capacity of 13.5 cubic feet. The cargo area is located in the trunk of the vehicle. To access the cargo area, open the trunk lid using the trunk release lever located in the driver's footwell. When loading cargo into the trunk, be sure to distribute the weight evenly. Do not overload the trunk, as this could affect the vehicle's handling and stability. Luggage === What's the emergency roadside assistance phone number? === The emergency roadside assistance phone number is 1-800-555-1212. SOURCE: Chapter 18: Emergencies Roadside Assistance If you experience a roadside emergency, such as a flat tire or a dead battery, you can call roadside assistance for help. Roadside assistance is available 24 hours a day, 7 days a week. To call roadside assistance, dial the following number: 1-800-555-1212 When you call roadside assistance, be prepared to provide the following information: Your name and contact information Your vehicle's make, model, and year Your vehicle's location === Are there some special kits available on that car? === Yes, the Cymbal Starlight comes with a tire repair kit. SOURCE: Lane keeping assist: This feature helps to keep you in your lane by gently steering the vehicle back into the lane if you start to drift. Adaptive cruise control: This feature automatically adjusts your speed to maintain a safe following distance from the vehicle in front of you. Forward collision warning: This feature warns you if you are approaching another vehicle too quickly. Automatic emergency braking: This feature can automatically apply the brakes to avoid a collision.
11. การเรียกใช้ฟังก์ชัน
มีบางสถานการณ์ที่คุณอาจต้องการให้ LLM เข้าถึงระบบภายนอก เช่น เว็บ API ระยะไกลที่ดึงข้อมูลหรือดำเนินการ หรือบริการที่ทำการคำนวณบางอย่าง เช่น
API เว็บระยะไกล
- ติดตามและอัปเดตคำสั่งซื้อของลูกค้า
- ค้นหาหรือสร้างตั๋วในเครื่องมือติดตามปัญหา
- ดึงข้อมูลแบบเรียลไทม์ เช่น ราคาหุ้นหรือการวัดค่าเซ็นเซอร์ IoT
- ส่งอีเมล
เครื่องมือคำนวณ
- เครื่องคิดเลขสำหรับโจทย์คณิตศาสตร์ที่ซับซ้อนมากขึ้น
- การตีความโค้ดเพื่อเรียกใช้โค้ดเมื่อ LLM ต้องการตรรกะการให้เหตุผล
- แปลงคำขอในภาษาธรรมชาติเป็นคำค้นหา SQL เพื่อให้ LLM ค้นหาฐานข้อมูลได้
การเรียกใช้ฟังก์ชัน (บางครั้งเรียกว่าเครื่องมือหรือการใช้เครื่องมือ) คือความสามารถของโมเดลในการขอให้มีการเรียกใช้ฟังก์ชันอย่างน้อย 1 รายการในนามของโมเดล เพื่อให้โมเดลตอบพรอมต์ของผู้ใช้ได้อย่างถูกต้องด้วยข้อมูลที่ใหม่กว่า
เมื่อได้รับพรอมต์ที่เฉพาะเจาะจงจากผู้ใช้ และความรู้เกี่ยวกับฟังก์ชันที่มีอยู่ซึ่งอาจเกี่ยวข้องกับบริบทนั้น LLM จะตอบกลับด้วยคำขอเรียกใช้ฟังก์ชันได้ จากนั้นแอปพลิเคชันที่ผสานรวม LLM จะเรียกใช้ฟังก์ชันในนามของแอปพลิเคชันนั้น แล้วตอบกลับ LLM ด้วยการตอบสนอง และ LLM จะตีความกลับโดยตอบกลับด้วยคำตอบที่เป็นข้อความ
4 ขั้นตอนของการเรียกฟังก์ชัน
มาดูตัวอย่างการเรียกใช้ฟังก์ชันกันดีกว่า นั่นคือการรับข้อมูลเกี่ยวกับพยากรณ์อากาศ
หากคุณถาม Gemini หรือ LLM อื่นๆ เกี่ยวกับสภาพอากาศในปารีส ระบบจะตอบกลับว่าไม่มีข้อมูลเกี่ยวกับพยากรณ์อากาศปัจจุบัน หากต้องการให้ LLM เข้าถึงข้อมูลสภาพอากาศแบบเรียลไทม์ คุณต้องกำหนดฟังก์ชันบางอย่างที่ LLM สามารถขอใช้ได้
ดูแผนภาพต่อไปนี้

1️⃣ ขั้นแรก ผู้ใช้ถามเกี่ยวกับสภาพอากาศในปารีส แอปแชทบอท (ใช้ LangChain4j) รู้ว่ามีฟังก์ชันอย่างน้อย 1 ฟังก์ชันที่พร้อมใช้งานเพื่อช่วยให้ LLM ตอบคำค้นหาได้ แชทบอทจะส่งทั้งพรอมต์เริ่มต้นและรายการฟังก์ชันที่เรียกใช้ได้ ในที่นี้ ฟังก์ชันชื่อ getWeather() จะใช้พารามิเตอร์สตริงสำหรับสถานที่ตั้ง

เนื่องจาก LLM ไม่ทราบข้อมูลพยากรณ์อากาศ จึงจะส่งคำขอการเรียกใช้ฟังก์ชันกลับมาแทนที่จะตอบกลับทางข้อความ แชทบอทต้องเรียกใช้ฟังก์ชัน getWeather() โดยมี "Paris" เป็นพารามิเตอร์ตำแหน่ง
2️⃣ แชทบ็อตเรียกใช้ฟังก์ชันดังกล่าวในนามของ LLM และดึงข้อมูลการตอบกลับของฟังก์ชัน ในที่นี้ เราจะสมมติว่าคำตอบคือ {"forecast": "sunny"}

3️⃣ แอปแชทบอทจะส่งการตอบกลับ JSON กลับไปยัง LLM

4️⃣ LLM จะดูการตอบกลับ JSON ตีความข้อมูลนั้น และตอบกลับด้วยข้อความที่ระบุว่าที่ปารีสมีแดดจัด

แต่ละขั้นตอนเป็นโค้ด
ก่อนอื่น คุณจะต้องกำหนดค่าโมเดล Gemini ตามปกติ
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.maxOutputTokens(100)
.build();
คุณกำหนดข้อกำหนดของเครื่องมือที่อธิบายฟังก์ชันที่เรียกใช้ได้
ToolSpecification weatherToolSpec = ToolSpecification.builder()
.name("getWeather")
.description("Get the weather forecast for a given location or city")
.parameters(JsonObjectSchema.builder()
.addStringProperty(
"location",
"the location or city to get the weather forecast for")
.build())
.build();
มีการกำหนดชื่อฟังก์ชัน รวมถึงชื่อและประเภทของพารามิเตอร์ แต่โปรดสังเกตว่าทั้งฟังก์ชันและพารามิเตอร์มีคำอธิบาย คำอธิบายมีความสำคัญมากและช่วยให้ LLM เข้าใจจริงๆ ว่าฟังก์ชันทำอะไรได้บ้าง จึงจะพิจารณาได้ว่าจำเป็นต้องเรียกใช้ฟังก์ชันนี้ในบริบทของการสนทนาหรือไม่
มาเริ่มขั้นตอนที่ 1 กันโดยส่งคำถามแรกเกี่ยวกับสภาพอากาศในปารีส
List<ChatMessage> allMessages = new ArrayList<>();
// 1) Ask the question about the weather
UserMessage weatherQuestion = UserMessage.from("What is the weather in Paris?");
allMessages.add(weatherQuestion);
ในขั้นตอนที่ 2 เราส่งเครื่องมือที่ต้องการให้โมเดลใช้ และโมเดลจะตอบกลับด้วยคำขอการดำเนินการเครื่องมือ
// 2) The model replies with a function call request
Response<AiMessage> messageResponse = model.generate(allMessages, weatherToolSpec);
ToolExecutionRequest toolExecutionRequest = messageResponse.content().toolExecutionRequests().getFirst();
System.out.println("Tool execution request: " + toolExecutionRequest);
allMessages.add(messageResponse.content());
ขั้นตอนที่ 3 ตอนนี้เรารู้แล้วว่า LLM ต้องการให้เราเรียกใช้ฟังก์ชันใด ในโค้ด เราไม่ได้เรียกใช้ API ภายนอกจริงๆ แต่เพียงแค่ส่งคืนพยากรณ์อากาศสมมติโดยตรง
// 3) We send back the result of the function call
ToolExecutionResultMessage toolExecResMsg = ToolExecutionResultMessage.from(toolExecutionRequest,
"{\"location\":\"Paris\",\"forecast\":\"sunny\", \"temperature\": 20}");
allMessages.add(toolExecResMsg);
และในขั้นตอนที่ 4 LLM จะเรียนรู้เกี่ยวกับผลการดำเนินการฟังก์ชัน และสามารถสังเคราะห์คำตอบที่เป็นข้อความได้
// 4) The model answers with a sentence describing the weather
Response<AiMessage> weatherResponse = model.generate(allMessages);
System.out.println("Answer: " + weatherResponse.content().text());
ซอร์สโค้ดทั้งหมดอยู่ใน FunctionCalling.java ในไดเรกทอรี app/src/main/java/gemini/workshop
เรียกใช้ตัวอย่าง
./gradlew run -q -DjavaMainClass=gemini.workshop.FunctionCalling
คุณควรเห็นเอาต์พุตที่คล้ายกับเอาต์พุตต่อไปนี้
Tool execution request: ToolExecutionRequest { id = null, name = "getWeatherForecast", arguments = "{"location":"Paris"}" }
Answer: The weather in Paris is sunny with a temperature of 20 degrees Celsius.
คุณจะเห็นคำขอการเรียกใช้เครื่องมือและคำตอบในเอาต์พุตด้านบน
12. LangChain4j จัดการการเรียกใช้ฟังก์ชัน
ในขั้นตอนก่อนหน้า คุณได้เห็นว่าการโต้ตอบของคำถาม/คำตอบที่เป็นข้อความปกติและคำขอ/การตอบกลับฟังก์ชันนั้นสลับกันอย่างไร และในระหว่างนั้น คุณได้ให้การตอบกลับฟังก์ชันที่ขอโดยตรงโดยไม่ต้องเรียกใช้ฟังก์ชันจริง
อย่างไรก็ตาม LangChain4j ยังมีการแยกข้อมูลระดับสูงกว่าซึ่งสามารถจัดการการเรียกใช้ฟังก์ชันให้คุณได้อย่างโปร่งใส ขณะเดียวกันก็จัดการการสนทนาตามปกติ
การเรียกฟังก์ชันเดียว
มาดู FunctionCallingAssistant.java กันทีละส่วน
ก่อนอื่น ให้สร้างบันทึกที่จะแสดงโครงสร้างข้อมูลการตอบสนองของฟังก์ชัน ดังนี้
record WeatherForecast(String location, String forecast, int temperature) {}
การตอบกลับจะมีข้อมูลเกี่ยวกับสถานที่ พยากรณ์อากาศ และอุณหภูมิ
จากนั้นสร้างคลาสที่มีฟังก์ชันจริงที่คุณต้องการทำให้โมเดลใช้ได้
static class WeatherForecastService {
@Tool("Get the weather forecast for a location")
WeatherForecast getForecast(@P("Location to get the forecast for") String location) {
if (location.equals("Paris")) {
return new WeatherForecast("Paris", "Sunny", 20);
} else if (location.equals("London")) {
return new WeatherForecast("London", "Rainy", 15);
} else {
return new WeatherForecast("Unknown", "Unknown", 0);
}
}
}
โปรดทราบว่าคลาสนี้มีฟังก์ชันเดียว แต่มีการใส่คำอธิบายประกอบด้วยคำอธิบายประกอบ @Tool ซึ่งสอดคล้องกับคำอธิบายของฟังก์ชันที่โมเดลขอเรียกใช้ได้
นอกจากนี้ ยังมีการใส่คำอธิบายประกอบพารามิเตอร์ของฟังก์ชัน (ในที่นี้มีเพียงรายการเดียว) ด้วยคำอธิบายประกอบ @P สั้นๆ นี้ ซึ่งจะอธิบายพารามิเตอร์ด้วย คุณสามารถเพิ่มฟังก์ชันได้มากเท่าที่ต้องการเพื่อให้โมเดลใช้งานได้ในสถานการณ์ที่ซับซ้อนมากขึ้น
ในคลาสนี้ คุณจะส่งคืนคำตอบที่บันทึกไว้ แต่หากต้องการเรียกใช้บริการพยากรณ์อากาศภายนอกจริง คุณจะต้องเรียกใช้บริการนั้นในเนื้อหาของเมธอด
ดังที่เราได้เห็นเมื่อคุณสร้าง ToolSpecification ในแนวทางก่อนหน้านี้ การบันทึกสิ่งที่ฟังก์ชันทำและอธิบายว่าพารามิเตอร์สอดคล้องกับอะไรเป็นสิ่งสำคัญ ซึ่งจะช่วยให้โมเดลเข้าใจวิธีและเวลาที่ใช้ฟังก์ชันนี้ได้
จากนั้น LangChain4j จะช่วยให้คุณระบุอินเทอร์เฟซที่สอดคล้องกับสัญญาที่คุณต้องการใช้เพื่อโต้ตอบกับโมเดลได้ ในที่นี้เป็นอินเทอร์เฟซที่เรียบง่ายซึ่งรับสตริงที่แสดงข้อความของผู้ใช้ และส่งคืนสตริงที่สอดคล้องกับการตอบกลับของโมเดล
interface WeatherAssistant {
String chat(String userMessage);
}
นอกจากนี้ คุณยังใช้ลายเซ็นที่ซับซ้อนกว่านี้ได้ด้วย ซึ่งเกี่ยวข้องกับ UserMessage ของ LangChain4j (สำหรับข้อความของผู้ใช้) หรือ AiMessage (สำหรับการตอบกลับของโมเดล) หรือแม้แต่ TokenStream หากต้องการจัดการกับสถานการณ์ที่ซับซ้อนกว่านี้ เนื่องจากออบเจ็กต์ที่ซับซ้อนกว่านั้นยังมีข้อมูลเพิ่มเติม เช่น จำนวนโทเค็นที่ใช้ เป็นต้น แต่เพื่อให้ง่าย เราจะใช้สตริงเป็นอินพุตและสตริงเป็นเอาต์พุต
มาปิดท้ายด้วยวิธี main() ที่เชื่อมโยงทุกอย่างเข้าด้วยกัน
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.build();
WeatherForecastService weatherForecastService = new WeatherForecastService();
WeatherAssistant assistant = AiServices.builder(WeatherAssistant.class)
.chatLanguageModel(model)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.tools(weatherForecastService)
.build();
System.out.println(assistant.chat("What is the weather in Paris?"));
System.out.println(assistant.chat("Is it warmer in London or in Paris?"));
}
คุณกำหนดค่าโมเดลแชทของ Gemini ได้ตามปกติ จากนั้นคุณจะสร้างอินสแตนซ์ของบริการพยากรณ์อากาศที่มี "ฟังก์ชัน" ที่โมเดลจะขอให้เราเรียกใช้
ตอนนี้คุณใช้AiServicesคลาสอีกครั้งเพื่อเชื่อมโยงโมเดลแชท หน่วยความจำแชท และเครื่องมือ (เช่น บริการพยากรณ์อากาศพร้อมฟังก์ชัน) AiServices จะแสดงผลออบเจ็กต์ที่ใช้ WeatherAssistant อินเทอร์เฟซที่คุณกำหนด สิ่งเดียวที่เหลืออยู่คือการเรียกใช้เมธอด chat() ของผู้ช่วยนั้น เมื่อเรียกใช้ คุณจะเห็นเฉพาะการตอบกลับที่เป็นข้อความ แต่คำขอการเรียกใช้ฟังก์ชันและการตอบกลับการเรียกใช้ฟังก์ชันจะไม่ปรากฏต่อผู้พัฒนา และระบบจะจัดการคำขอเหล่านั้นโดยอัตโนมัติและโปร่งใส หาก Gemini คิดว่าควรเรียกใช้ฟังก์ชัน Gemini จะตอบกลับพร้อมคำขอเรียกใช้ฟังก์ชัน และ LangChain4j จะจัดการการเรียกใช้ฟังก์ชันในเครื่องในนามของคุณ
เรียกใช้ตัวอย่าง
./gradlew run -q -DjavaMainClass=gemini.workshop.FunctionCallingAssistant
คุณควรเห็นเอาต์พุตที่คล้ายกับเอาต์พุตต่อไปนี้
OK. The weather in Paris is sunny with a temperature of 20 degrees.
It is warmer in Paris (20 degrees) than in London (15 degrees).
นี่เป็นตัวอย่างของฟังก์ชันเดียว
การเรียกฟังก์ชันหลายรายการ
นอกจากนี้ คุณยังมีฟังก์ชันหลายรายการและให้ LangChain4j จัดการการเรียกฟังก์ชันหลายรายการในนามของคุณได้ด้วย ดูตัวอย่างฟังก์ชันหลายรายการได้ที่ MultiFunctionCallingAssistant.java
โดยมีฟังก์ชันในการแปลงสกุลเงินดังนี้
@Tool("Convert amounts between two currencies")
double convertCurrency(
@P("Currency to convert from") String fromCurrency,
@P("Currency to convert to") String toCurrency,
@P("Amount to convert") double amount) {
double result = amount;
if (fromCurrency.equals("USD") && toCurrency.equals("EUR")) {
result = amount * 0.93;
} else if (fromCurrency.equals("USD") && toCurrency.equals("GBP")) {
result = amount * 0.79;
}
System.out.println(
"convertCurrency(fromCurrency = " + fromCurrency +
", toCurrency = " + toCurrency +
", amount = " + amount + ") == " + result);
return result;
}
ฟังก์ชันอื่นในการรับค่าของหุ้น
@Tool("Get the current value of a stock in US dollars")
double getStockPrice(@P("Stock symbol") String symbol) {
double result = 170.0 + 10 * new Random().nextDouble();
System.out.println("getStockPrice(symbol = " + symbol + ") == " + result);
return result;
}
อีกฟังก์ชันหนึ่งที่ใช้เปอร์เซ็นต์กับจำนวนเงินที่ระบุ
@Tool("Apply a percentage to a given amount")
double applyPercentage(@P("Initial amount") double amount, @P("Percentage between 0-100 to apply") double percentage) {
double result = amount * (percentage / 100);
System.out.println("applyPercentage(amount = " + amount + ", percentage = " + percentage + ") == " + result);
return result;
}
จากนั้นคุณสามารถรวมฟังก์ชันทั้งหมดนี้และคลาส MultiTools แล้วถามคำถาม เช่น "ราคาหุ้น AAPL 10% เมื่อแปลงจาก USD เป็น EUR คือเท่าใด"
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.maxOutputTokens(100)
.build();
MultiTools multiTools = new MultiTools();
MultiToolsAssistant assistant = AiServices.builder(MultiToolsAssistant.class)
.chatLanguageModel(model)
.chatMemory(withMaxMessages(10))
.tools(multiTools)
.build();
System.out.println(assistant.chat(
"What is 10% of the AAPL stock price converted from USD to EUR?"));
}
เรียกใช้โดยทำดังนี้
./gradlew run -q -DjavaMainClass=gemini.workshop.MultiFunctionCallingAssistant
และคุณควรเห็นฟังก์ชันหลายรายการที่เรียกใช้ดังนี้
getStockPrice(symbol = AAPL) == 172.8022224055534 convertCurrency(fromCurrency = USD, toCurrency = EUR, amount = 172.8022224055534) == 160.70606683716468 applyPercentage(amount = 160.70606683716468, percentage = 10.0) == 16.07060668371647 10% of the AAPL stock price converted from USD to EUR is 16.07060668371647 EUR.
สู่เอเจนต์
การเรียกใช้ฟังก์ชันเป็นกลไกการขยายที่ยอดเยี่ยมสำหรับโมเดลภาษาขนาดใหญ่ เช่น Gemini ซึ่งช่วยให้เราสร้างระบบที่ซับซ้อนมากขึ้นได้ โดยมักเรียกว่า "เอเจนต์" หรือ "ผู้ช่วย AI" เอเจนต์เหล่านี้สามารถโต้ตอบกับโลกภายนอกผ่าน API ภายนอกและกับบริการที่อาจส่งผลข้างเคียงต่อสภาพแวดล้อมภายนอก (เช่น การส่งอีเมล การสร้างตั๋ว ฯลฯ)
เมื่อสร้างเอเจนต์ที่มีประสิทธิภาพเช่นนี้ คุณควรสร้างอย่างมีความรับผิดชอบ คุณควรพิจารณาการมีส่วนร่วมของเจ้าหน้าที่ก่อนที่จะดำเนินการอัตโนมัติ การคำนึงถึงความปลอดภัยเป็นสิ่งสำคัญเมื่อออกแบบเอเจนต์ที่ทำงานด้วย LLM ซึ่งโต้ตอบกับโลกภายนอก
13. การเรียกใช้ Gemma ด้วย Ollama และ TestContainers
ที่ผ่านมาเราใช้ Gemini แต่ก็มี Gemma ซึ่งเป็นโมเดลน้องสาวด้วย
Gemma เป็นกลุ่มผลิตภัณฑ์โมเดลแบบเปิดที่มีน้ำหนักเบาและล้ำสมัย ซึ่งสร้างขึ้นจากการวิจัยและเทคโนโลยีเดียวกันกับที่ใช้สร้างโมเดล Gemini โมเดล Gemma ล่าสุดคือ Gemma3 ซึ่งมีให้เลือก 4 ขนาด ได้แก่ 1B (ข้อความเท่านั้น), 4B, 12B และ 27B โดยน้ำหนักของโมเดลนั้นพร้อมให้ใช้งานได้อย่างอิสระ และขนาดเล็กหมายความว่าคุณสามารถเรียกใช้ได้ด้วยตนเอง แม้แต่ในแล็ปท็อปหรือใน Cloud Shell
คุณเรียกใช้ Gemma อย่างไร
คุณเรียกใช้ Gemma ได้หลายวิธี ไม่ว่าจะเป็นในระบบคลาวด์ ผ่าน Vertex AI ด้วยการคลิกปุ่ม หรือ GKE ด้วย GPU บางตัว แต่คุณก็เรียกใช้ในเครื่องได้เช่นกัน
ตัวเลือกที่ดีอย่างหนึ่งในการเรียกใช้ Gemma ในเครื่องคือการใช้ Ollama ซึ่งเป็นเครื่องมือที่ช่วยให้คุณเรียกใช้โมเดลขนาดเล็ก เช่น Llama, Mistral และอื่นๆ อีกมากมายในเครื่องของคุณได้ ซึ่งคล้ายกับ Docker แต่ใช้กับ LLM
ติดตั้ง Ollama โดยทำตามวิธีการสำหรับระบบปฏิบัติการของคุณ
หากใช้สภาพแวดล้อม Linux คุณจะต้องเปิดใช้ Ollama ก่อนหลังจากติดตั้ง
ollama serve > /dev/null 2>&1 &
เมื่อติดตั้งในเครื่องแล้ว คุณจะเรียกใช้คำสั่งเพื่อดึงโมเดลได้โดยทำดังนี้
ollama pull gemma3:1b
รอให้ระบบดึงโมเดล ซึ่งอาจใช้เวลาสักครู่
เรียกใช้โมเดล
ollama run gemma3:1b
ตอนนี้คุณสามารถโต้ตอบกับโมเดลได้แล้วโดยทำดังนี้
>>> Hello! Hello! It's nice to hear from you. What can I do for you today?
หากต้องการออกจากพรอมต์ ให้กด Ctrl+D
การเรียกใช้ Gemma ใน Ollama บน TestContainers
คุณสามารถใช้ Ollama ภายในคอนเทนเนอร์ที่จัดการโดย TestContainers แทนที่จะต้องติดตั้งและเรียกใช้ Ollama ในเครื่อง
TestContainers ไม่ได้มีประโยชน์แค่สำหรับการทดสอบเท่านั้น แต่คุณยังใช้เพื่อเรียกใช้คอนเทนเนอร์ได้ด้วย นอกจากนี้ ยังมีOllamaContainerเฉพาะที่คุณใช้ประโยชน์ได้ด้วย
ภาพรวมทั้งหมดมีดังนี้

การใช้งาน
มาดู GemmaWithOllamaContainer.java กันทีละส่วน
ก่อนอื่น คุณต้องสร้างคอนเทนเนอร์ Ollama ที่ได้มาซึ่งดึงโมเดล Gemma เข้ามา รูปภาพนี้มีอยู่แล้วจากการเรียกใช้ครั้งก่อน หรือจะมีการสร้างขึ้น หากมีรูปภาพอยู่แล้ว คุณเพียงแค่ต้องบอก TestContainers ว่าต้องการแทนที่รูปภาพ Ollama เริ่มต้นด้วยตัวแปรที่ขับเคลื่อนด้วย Gemma
private static final String TC_OLLAMA_GEMMA3 = "tc-ollama-gemma3-1b";
public static final String GEMMA_3 = "gemma3:1b";
// Creating an Ollama container with Gemma 3 if it doesn't exist.
private static OllamaContainer createGemmaOllamaContainer() throws IOException, InterruptedException {
// Check if the custom Gemma Ollama image exists already
List<Image> listImagesCmd = DockerClientFactory.lazyClient()
.listImagesCmd()
.withImageNameFilter(TC_OLLAMA_GEMMA3)
.exec();
if (listImagesCmd.isEmpty()) {
System.out.println("Creating a new Ollama container with Gemma 3 image...");
OllamaContainer ollama = new OllamaContainer("ollama/ollama:0.7.1");
System.out.println("Starting Ollama...");
ollama.start();
System.out.println("Pulling model...");
ollama.execInContainer("ollama", "pull", GEMMA_3);
System.out.println("Committing to image...");
ollama.commitToImage(TC_OLLAMA_GEMMA3);
return ollama;
}
System.out.println("Ollama image substitution...");
// Substitute the default Ollama image with our Gemma variant
return new OllamaContainer(
DockerImageName.parse(TC_OLLAMA_GEMMA3)
.asCompatibleSubstituteFor("ollama/ollama"));
}
จากนั้นสร้างและเริ่มคอนเทนเนอร์ทดสอบ Ollama แล้วสร้างโมเดลแชท Ollama โดยชี้ไปที่ที่อยู่และพอร์ตของคอนเทนเนอร์ที่มีโมเดลที่คุณต้องการใช้ สุดท้ายนี้ คุณเพียงแค่เรียกใช้ model.generate(yourPrompt) ตามปกติ
public static void main(String[] args) throws IOException, InterruptedException {
OllamaContainer ollama = createGemmaOllamaContainer();
ollama.start();
ChatLanguageModel model = OllamaChatModel.builder()
.baseUrl(String.format("http://%s:%d", ollama.getHost(), ollama.getFirstMappedPort()))
.modelName(GEMMA_3)
.build();
String response = model.generate("Why is the sky blue?");
System.out.println(response);
}
เรียกใช้โดยทำดังนี้
./gradlew run -q -DjavaMainClass=gemini.workshop.GemmaWithOllamaContainer
การเรียกใช้ครั้งแรกจะใช้เวลาสักครู่ในการสร้างและเรียกใช้คอนเทนเนอร์ แต่เมื่อเสร็จแล้ว คุณควรเห็น Gemma ตอบกลับดังนี้
INFO: Container ollama/ollama:0.7.1 started in PT7.228339916S
The sky appears blue due to Rayleigh scattering. Rayleigh scattering is a phenomenon that occurs when sunlight interacts with molecules in the Earth's atmosphere.
* **Scattering particles:** The main scattering particles in the atmosphere are molecules of nitrogen (N2) and oxygen (O2).
* **Wavelength of light:** Blue light has a shorter wavelength than other colors of light, such as red and yellow.
* **Scattering process:** When blue light interacts with these molecules, it is scattered in all directions.
* **Human eyes:** Our eyes are more sensitive to blue light than other colors, so we perceive the sky as blue.
This scattering process results in a blue appearance for the sky, even though the sun is actually emitting light of all colors.
In addition to Rayleigh scattering, other atmospheric factors can also influence the color of the sky, such as dust particles, aerosols, and clouds.
คุณมี Gemma ที่ทำงานใน Cloud Shell แล้ว
14. ขอแสดงความยินดี
ขอแสดงความยินดี คุณสร้างแอปพลิเคชันแชท Generative AI ตัวแรกใน Java โดยใช้ LangChain4j และ Gemini API ได้สำเร็จแล้ว คุณได้ค้นพบระหว่างทางว่าโมเดลภาษาขนาดใหญ่แบบมัลติโมดัลนั้นมีประสิทธิภาพมากและสามารถจัดการงานต่างๆ ได้ เช่น การถาม/ตอบ แม้แต่ในเอกสารประกอบของคุณเอง การดึงข้อมูล การโต้ตอบกับ API ภายนอก และอื่นๆ
สิ่งต่อไปที่ควรทำ
ถึงเวลาที่คุณจะเพิ่มประสิทธิภาพแอปพลิเคชันด้วยการผสานรวม LLM ที่ทรงพลังแล้ว
อ่านเพิ่มเติม
- กรณีการใช้งานทั่วไปของ Generative AI
- แหล่งข้อมูลการฝึกอบรมเกี่ยวกับ Generative AI
- โต้ตอบกับ Gemini ผ่าน Generative AI Studio
- AI ที่มีความรับผิดชอบ