1. Pengantar
Codelab ini berfokus pada Model Bahasa Besar (LLM) Gemini, yang dihosting di Vertex AI di Google Cloud. Vertex AI adalah platform yang mencakup semua produk, layanan, dan model machine learning di Google Cloud.
Anda akan menggunakan Java untuk berinteraksi dengan Gemini API menggunakan framework LangChain4j. Anda akan mempelajari contoh konkret untuk memanfaatkan LLM dalam menjawab pertanyaan, menghasilkan ide, mengekstrak entitas dan konten terstruktur, retrieval augmented generation, dan panggilan fungsi.
Apa itu AI Generatif?
AI generatif mengacu pada penggunaan kecerdasan buatan untuk membuat konten baru, seperti teks, gambar, musik, audio, dan video.
AI generatif didukung oleh model bahasa besar (LLM) yang dapat melakukan beberapa tugas sekaligus dan melakukan tugas siap pakai seperti perangkuman, tanya jawab, klasifikasi, dan sebagainya. Dengan sedikit pelatihan, model dasar dapat diadaptasi untuk kasus penggunaan yang ditargetkan dengan data contoh yang sangat sedikit.
Bagaimana cara kerja AI Generatif?
AI generatif bekerja menggunakan model Machine Learning (ML) untuk mempelajari pola dan hubungan dalam set data yang terdiri dari konten buatan manusia. Sistem ini kemudian menggunakan pola-pola yang telah dipelajarinya untuk membuat konten baru.
Cara yang paling umum digunakan untuk melatih model AI generatif adalah dengan menggunakan pembelajaran yang diawasi. Model diberi satu set konten buatan manusia dan label yang sesuai. AI generatif kemudian belajar membuat konten yang serupa dengan konten buatan manusia tersebut.
Apa saja penerapan umum AI Generatif?
AI generatif dapat digunakan untuk:
- Meningkatkan interaksi pelanggan melalui chat dan pengalaman penelusuran yang ditingkatkan kualitasnya.
- Menjelajahi jumlah data tak terstruktur yang sangat banyak melalui antarmuka percakapan dan perangkuman.
- Membantu pelaksanaan tugas berulang, seperti membalas permintaan proposal, melokalkan konten pemasaran dalam berbagai bahasa, dan memeriksa kontrak pelanggan terkait kepatuhan, dan lainnya.
Penawaran AI Generatif seperti apa yang dimiliki Google Cloud?
Dengan Vertex AI, Anda dapat berinteraksi dengan model dasar, menyesuaikannya, dan menyematkannya ke aplikasi Anda dengan sedikit atau tanpa keahlian ML. Anda dapat mengakses model dasar di Model Garden, menyesuaikan model melalui UI sederhana di Vertex AI Studio, atau menggunakan model di notebook data science.
Vertex AI Search and Conversation menawarkan cara tercepat bagi para developer untuk membangun mesin telusur dan chatbot yang didukung teknologi AI generatif.
Didukung oleh Gemini, Gemini untuk Google Cloud adalah kolaborator yang didukung teknologi AI yang tersedia di seluruh Google Cloud dan IDE untuk membantu Anda menyelesaikan lebih banyak pekerjaan dengan lebih cepat. Gemini Code Assist menyediakan penyelesaian kode, pembuatan kode, penjelasan kode, dan memungkinkan Anda melakukan chat dengannya untuk mengajukan pertanyaan teknis.
Apa itu Gemini?
Gemini adalah rangkaian model AI generatif yang dikembangkan oleh Google DeepMind dan dirancang untuk kasus penggunaan multimodal. Multimodal berarti model ini dapat memproses dan menghasilkan berbagai jenis konten seperti teks, kode, gambar, dan audio.

Gemini hadir dalam berbagai variasi dan ukuran:
- Gemini 2.0 Flash: Fitur generasi berikutnya terbaru dan kemampuan yang ditingkatkan.
- Gemini 2.0 Flash-Lite: Model Gemini 2.0 Flash yang dioptimalkan untuk efisiensi biaya dan latensi rendah.
- Gemini 2.5 Pro: Model penalaran tercanggih kami hingga saat ini.
- Gemini 2.5 Flash: Model penalaran yang menawarkan kemampuan serba guna. Jenis ini dirancang untuk menawarkan keseimbangan antara harga dan performa.
Fitur Utama:
- Multimodalitas: Kemampuan Gemini untuk memahami dan menangani berbagai format informasi merupakan langkah signifikan di luar model bahasa tradisional yang hanya berbasis teks.
- Performa: Gemini 2.5 Pro mengungguli model tercanggih saat ini dalam banyak tolok ukur dan merupakan model pertama yang melampaui pakar manusia dalam tolok ukur MMLU (Massive Multitask Language Understanding) yang menantang.
- Fleksibilitas: Berbagai ukuran Gemini membuatnya dapat disesuaikan untuk berbagai kasus penggunaan, mulai dari riset skala besar hingga deployment di perangkat seluler.
Bagaimana cara berinteraksi dengan Gemini di Vertex AI dari Java?
Anda memiliki dua opsi:
- Library Vertex AI Java API for Gemini resmi.
- Framework LangChain4j.
Dalam codelab ini, Anda akan menggunakan framework LangChain4j.
Apa itu framework LangChain4j?
Framework LangChain4j adalah library open source untuk mengintegrasikan LLM dalam aplikasi Java Anda, dengan mengorkestrasi berbagai komponen, seperti LLM itu sendiri, tetapi juga alat lain seperti database vektor (untuk penelusuran semantik), loader dan splitter dokumen (untuk menganalisis dokumen dan mempelajarinya), parser output, dan lainnya.
Project ini terinspirasi oleh project Python LangChain, tetapi dengan tujuan untuk melayani developer Java.

Yang akan Anda pelajari
- Cara menyiapkan project Java untuk menggunakan Gemini dan LangChain4j
- Cara mengirim perintah pertama Anda ke Gemini secara terprogram
- Cara menampilkan respons dari Gemini secara bertahap
- Cara memulai percakapan antara pengguna dan Gemini
- Cara menggunakan Gemini dalam konteks multimodal dengan mengirimkan teks dan gambar
- Cara mengekstrak informasi terstruktur yang berguna dari konten tidak terstruktur
- Cara memanipulasi template perintah
- Cara melakukan klasifikasi teks seperti analisis sentimen
- Cara melakukan chat dengan dokumen Anda sendiri (Retrieval-Augmented Generation)
- Cara memperluas chatbot Anda dengan panggilan fungsi
- Cara menggunakan Gemma secara lokal dengan Ollama dan TestContainers
Yang Anda butuhkan
- Pengetahuan tentang bahasa pemrograman Java
- Project Google Cloud
- Browser, seperti Chrome atau Firefox
2. Penyiapan dan persyaratan
Penyiapan lingkungan mandiri
- Login ke Google Cloud Console dan buat project baru atau gunakan kembali project yang sudah ada. Jika belum memiliki akun Gmail atau Google Workspace, Anda harus membuatnya.



- Project name adalah nama tampilan untuk peserta project ini. String ini adalah string karakter yang tidak digunakan oleh Google API. Anda dapat memperbaruinya kapan saja.
- Project ID bersifat unik di semua project Google Cloud dan tidak dapat diubah (tidak dapat diubah setelah ditetapkan). Cloud Console otomatis membuat string unik; biasanya Anda tidak mementingkan kata-katanya. Di sebagian besar codelab, Anda harus merujuk Project ID-nya (umumnya diidentifikasi sebagai
PROJECT_ID). Jika tidak suka dengan ID yang dibuat, Anda dapat membuat ID acak lainnya. Atau, Anda dapat mencobanya sendiri, dan lihat apakah ID tersebut tersedia. ID tidak dapat diubah setelah langkah ini dan tersedia selama durasi project. - Sebagai informasi, ada nilai ketiga, Project Number, yang digunakan oleh beberapa API. Pelajari lebih lanjut ketiga nilai ini di dokumentasi.
- Selanjutnya, Anda harus mengaktifkan penagihan di Konsol Cloud untuk menggunakan resource/API Cloud. Menjalankan operasi dalam codelab ini tidak akan memakan banyak biaya, bahkan mungkin tidak sama sekali. Guna mematikan resource agar tidak menimbulkan penagihan di luar tutorial ini, Anda dapat menghapus resource yang dibuat atau menghapus project-nya. Pengguna baru Google Cloud memenuhi syarat untuk mengikuti program Uji Coba Gratis senilai $300 USD.
Mulai Cloud Shell
Meskipun Google Cloud dapat dioperasikan dari jarak jauh menggunakan laptop Anda, dalam codelab ini, Anda akan menggunakan Cloud Shell, lingkungan command line yang berjalan di Cloud.
Mengaktifkan Cloud Shell
- Dari Cloud Console, klik Aktifkan Cloud Shell
.

Jika ini adalah pertama kalinya Anda memulai Cloud Shell, Anda akan melihat layar perantara yang menjelaskan apa itu Cloud Shell. Jika Anda melihat layar perantara, klik Continue.

Perlu waktu beberapa saat untuk menyediakan dan terhubung ke Cloud Shell.

Virtual machine ini dilengkapi dengan semua alat pengembangan yang diperlukan. VM ini menawarkan direktori beranda tetap sebesar 5 GB dan beroperasi di Google Cloud, sehingga sangat meningkatkan performa dan autentikasi jaringan. Sebagian besar pekerjaan Anda dalam codelab ini dapat dilakukan dengan browser.
Setelah terhubung ke Cloud Shell, Anda akan melihat bahwa Anda telah diautentikasi dan project telah ditetapkan ke project ID Anda.
- Jalankan perintah berikut di Cloud Shell untuk mengonfirmasi bahwa Anda telah diautentikasi:
gcloud auth list
Output perintah
Credentialed Accounts
ACTIVE ACCOUNT
* <my_account>@<my_domain.com>
To set the active account, run:
$ gcloud config set account `ACCOUNT`
- Jalankan perintah berikut di Cloud Shell untuk mengonfirmasi bahwa perintah gcloud mengetahui project Anda:
gcloud config list project
Output perintah
[core] project = <PROJECT_ID>
Jika tidak, Anda dapat menyetelnya dengan perintah ini:
gcloud config set project <PROJECT_ID>
Output perintah
Updated property [core/project].
3. Menyiapkan lingkungan pengembangan Anda
Dalam codelab ini, Anda akan menggunakan terminal Cloud Shell dan editor Cloud Shell untuk mengembangkan program Java.
Mengaktifkan Vertex AI API
Di konsol Google Cloud, pastikan nama project Anda ditampilkan di bagian atas konsol Google Cloud. Jika tidak, klik Pilih project untuk membuka Pemilih Project, lalu pilih project yang Anda inginkan.
Anda dapat mengaktifkan Vertex AI API dari bagian Vertex AI di Konsol Google Cloud atau dari terminal Cloud Shell.
Untuk mengaktifkan dari konsol Google Cloud, pertama-tama, buka bagian Vertex AI di menu konsol Google Cloud:

Klik Enable All Recommended APIs di dasbor Vertex AI.
Tindakan ini akan mengaktifkan beberapa API, tetapi yang paling penting untuk codelab ini adalah aiplatform.googleapis.com.
Atau, Anda juga dapat mengaktifkan API ini dari terminal Cloud Shell dengan perintah berikut:
gcloud services enable aiplatform.googleapis.com
Clone repositori GitHub
Di terminal Cloud Shell, clone repositori untuk codelab ini:
git clone https://github.com/glaforge/gemini-workshop-for-java-developers.git
Untuk memeriksa apakah project siap dijalankan, Anda dapat mencoba menjalankan program "Hello World".
Pastikan Anda berada di folder tingkat teratas:
cd gemini-workshop-for-java-developers/
Buat wrapper Gradle:
gradle wrapper
Jalankan dengan gradlew:
./gradlew run
Anda akan melihat output berikut:
.. > Task :app:run Hello World!
Membuka dan menyiapkan Cloud Editor
Buka kode dengan Cloud Code Editor dari Cloud Shell:

Di Cloud Code Editor, buka folder sumber codelab dengan memilih File -> Open Folder dan arahkan ke folder sumber codelab (mis. /home/username/gemini-workshop-for-java-developers/).
Menyiapkan variabel lingkungan
Buka terminal baru di Cloud Code Editor dengan memilih Terminal -> New Terminal. Siapkan dua variabel lingkungan yang diperlukan untuk menjalankan contoh kode:
- PROJECT_ID — Project ID Google Cloud Anda
- LOCATION — Region tempat model Gemini di-deploy
Ekspor variabel sebagai berikut:
export PROJECT_ID=$(gcloud config get-value project) export LOCATION=us-central1
4. Panggilan pertama ke model Gemini
Setelah project disiapkan dengan benar, saatnya memanggil Gemini API.
Lihat QA.java di direktori 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?"));
}
}
Dalam contoh pertama ini, Anda perlu mengimpor class VertexAiGeminiChatModel, yang menerapkan antarmuka ChatModel.
Dalam metode main, Anda mengonfigurasi model bahasa chat menggunakan builder untuk VertexAiGeminiChatModel dan menentukan:
- Project
- Lokasi
- Nama model (
gemini-2.0-flash).
Setelah model bahasa siap, Anda dapat memanggil metode generate() dan meneruskan perintah, pertanyaan, atau petunjuk untuk dikirim ke LLM. Di sini, Anda mengajukan pertanyaan sederhana tentang apa yang membuat langit berwarna biru.
Jangan ragu untuk mengubah perintah ini untuk mencoba pertanyaan atau tugas yang berbeda.
Jalankan contoh di folder root kode sumber:
./gradlew run -q -DjavaMainClass=gemini.workshop.QA
Anda akan melihat output yang mirip dengan ini:
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.
Selamat, Anda telah melakukan panggilan pertama ke Gemini.
Respons streaming
Apakah Anda memperhatikan bahwa respons diberikan sekaligus, setelah beberapa detik? Respons juga dapat diperoleh secara bertahap, berkat varian respons streaming. Respons streaming, model akan menampilkan respons sepotong demi sepotong, saat respons tersebut tersedia.
Dalam codelab ini, kita akan menggunakan respons non-streaming, tetapi mari kita lihat respons streaming untuk melihat cara melakukannya.
Di StreamQA.java di direktori app/src/main/java/gemini/workshop, Anda dapat melihat respons streaming beraksi:
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));
}
}
Kali ini, kita mengimpor varian class streaming VertexAiGeminiStreamingChatModel yang menerapkan antarmuka StreamingChatLanguageModel. Anda juga perlu mengimpor statis LambdaStreamingResponseHandler.onNext yang merupakan metode praktis yang menyediakan StreamingResponseHandler untuk membuat handler streaming dengan ekspresi lambda Java.
Kali ini, tanda tangan metode generate() sedikit berbeda. Daripada menampilkan string, jenis nilai yang ditampilkan adalah void. Selain perintah, Anda harus meneruskan handler respons streaming. Di sini, berkat impor statis yang kami sebutkan di atas, kita dapat menentukan ekspresi lambda yang Anda teruskan ke metode onNext(). Ekspresi lambda dipanggil setiap kali bagian respons baru tersedia, sedangkan yang terakhir dipanggil hanya jika terjadi error.
Jalankan:
./gradlew run -q -DjavaMainClass=gemini.workshop.StreamQA
Anda akan mendapatkan jawaban yang serupa dengan kelas sebelumnya, tetapi kali ini, Anda akan melihat bahwa jawaban muncul secara progresif di shell, bukan menunggu tampilan jawaban lengkap.
Konfigurasi tambahan
Untuk konfigurasi, kita hanya menentukan project, lokasi, dan nama model, tetapi ada parameter lain yang dapat Anda tentukan untuk model:
temperature(Float temp)— untuk menentukan seberapa kreatif respons yang Anda inginkan (0 berarti kreativitas rendah dan sering kali lebih faktual, sedangkan 2 berarti output yang lebih kreatif)topP(Float topP)— untuk memilih kemungkinan kata yang total probabilitasnya sama dengan angka floating point tersebut (antara 0 dan 1)topK(Integer topK)— untuk memilih kata secara acak dari jumlah maksimum kata yang mungkin untuk penyelesaian teks (dari 1 hingga 40)maxOutputTokens(Integer max)— untuk menentukan panjang maksimum jawaban yang diberikan oleh model (umumnya, 4 token mewakili sekitar 3 kata)maxRetries(Integer retries)— jika Anda melampaui kuota permintaan per waktu, atau platform mengalami masalah teknis, Anda dapat meminta model mencoba kembali panggilan 3 kali
Sejauh ini, Anda mengajukan satu pertanyaan kepada Gemini, tetapi Anda juga dapat melakukan percakapan multi-turn. Itulah yang akan Anda pelajari di bagian berikutnya.
5. Mulai percakapan dengan Gemini
Pada langkah sebelumnya, Anda mengajukan satu pertanyaan. Sekarang saatnya melakukan percakapan nyata antara pengguna dan LLM. Setiap pertanyaan dan jawaban dapat dibuat berdasarkan pertanyaan dan jawaban sebelumnya untuk membentuk diskusi yang nyata.
Lihat Conversation.java di folder 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));
});
}
}
Beberapa impor baru yang menarik di kelas ini:
MessageWindowChatMemory— class yang akan membantu menangani aspek multi-turn dalam percakapan, dan menyimpan pertanyaan dan jawaban sebelumnya dalam memori lokalAiServices— class abstraksi tingkat yang lebih tinggi yang akan mengikat model chat dan memori chat
Di metode utama, Anda akan menyiapkan model, memori chat, dan layanan AI. Model dikonfigurasi seperti biasa dengan informasi project, lokasi, dan nama model.
Untuk memori chat, kita menggunakan builder MessageWindowChatMemory untuk membuat memori yang menyimpan 20 pesan terakhir yang dipertukarkan. Ini adalah jendela geser di atas percakapan yang konteksnya disimpan secara lokal di klien class Java kami.
Kemudian, Anda membuat AI service yang mengikat model chat dengan memori chat.
Perhatikan bagaimana layanan AI menggunakan antarmuka ConversationService kustom yang telah kita tetapkan, yang diimplementasikan oleh LangChain4j, dan yang mengambil kueri String serta menampilkan respons String.
Sekarang, saatnya memulai percakapan dengan Gemini. Pertama, ucapan salam sederhana dikirim, lalu pertanyaan pertama tentang Menara Eiffel untuk mengetahui di negara mana menara tersebut berada. Perhatikan bahwa kalimat terakhir terkait dengan jawaban pertanyaan pertama, karena Anda bertanya-tanya berapa banyak penduduk di negara tempat Menara Eiffel berada, tanpa menyebutkan secara eksplisit negara yang diberikan dalam jawaban sebelumnya. Hal ini menunjukkan bahwa pertanyaan dan jawaban sebelumnya dikirim dengan setiap perintah.
Jalankan contoh:
./gradlew run -q -DjavaMainClass=gemini.workshop.Conversation
Anda akan melihat tiga jawaban yang mirip dengan jawaban berikut:
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.
Anda dapat mengajukan pertanyaan satu putaran atau melakukan percakapan multi-putaran dengan Gemini, tetapi sejauh ini, inputnya hanya berupa teks. Bagaimana dengan gambar? Mari kita pelajari gambar pada langkah berikutnya.
6. Multimodalitas dengan Gemini
Gemini adalah model multimodal. Tidak hanya menerima teks sebagai input, tetapi juga menerima gambar, atau bahkan video sebagai input. Di bagian ini, Anda akan melihat kasus penggunaan untuk menggabungkan teks dan gambar.
Menurut Anda, apakah Gemini akan mengenali kucing ini?

Gambar kucing di salju yang diambil dari Wikipedia
Lihat Multimodal.java di direktori 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());
}
}
Dalam impor, perhatikan bahwa kita membedakan berbagai jenis pesan dan konten. UserMessage dapat berisi objek TextContent dan ImageContent. Inilah multimodality yang sedang bekerja: menggabungkan teks dan gambar. Kami tidak hanya mengirimkan perintah string sederhana, tetapi juga mengirimkan objek yang lebih terstruktur yang merepresentasikan pesan pengguna, yang terdiri dari bagian konten gambar dan bagian konten teks. Model mengirim kembali Response yang berisi AiMessage.
Kemudian, Anda mengambil AiMessage dari respons melalui content(), lalu teks pesan berkat text().
Jalankan contoh:
./gradlew run -q -DjavaMainClass=gemini.workshop.Multimodal
Nama gambar tersebut tentu memberi Anda petunjuk tentang isi gambar, tetapi output Gemini mirip dengan berikut:
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.
Menggabungkan gambar dan perintah teks membuka kasus penggunaan yang menarik. Anda dapat membuat aplikasi yang dapat:
- Mengenali teks dalam gambar.
- Periksa apakah gambar aman untuk ditampilkan.
- Membuat teks gambar.
- Menelusuri database gambar dengan deskripsi teks biasa.
Selain mengekstrak informasi dari gambar, Anda juga dapat mengekstrak informasi dari teks tidak terstruktur. Itulah yang akan Anda pelajari di bagian berikutnya.
7. Mengekstrak informasi terstruktur dari teks tidak terstruktur
Ada banyak situasi di mana informasi penting diberikan dalam dokumen laporan, email, atau teks panjang lainnya secara tidak terstruktur. Idealnya, Anda ingin dapat mengekstrak detail utama yang terdapat dalam teks tidak terstruktur, dalam bentuk objek terstruktur. Mari kita lihat cara melakukannya.
Misalkan Anda ingin mengekstrak nama dan usia seseorang, berdasarkan biografi, CV, atau deskripsi orang tersebut. Anda dapat menginstruksikan LLM untuk mengekstrak JSON dari teks tidak terstruktur dengan perintah yang disesuaikan secara cerdas (ini biasanya disebut "rekayasa perintah").
Namun, dalam contoh di bawah, alih-alih membuat perintah yang menjelaskan output JSON, kita akan menggunakan fitur canggih Gemini yang disebut output terstruktur, atau terkadang pembuatan yang dibatasi, yang memaksa model untuk hanya menghasilkan konten JSON yang valid, mengikuti skema JSON yang ditentukan.
Lihat ExtractData.java di 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
}
}
Mari kita lihat berbagai langkah dalam file ini:
- Data
Personditentukan untuk merepresentasikan detail yang menjelaskan seseorang (nama dan usia). - Antarmuka
PersonExtractorditentukan dengan metode yang, jika diberi string teks tidak terstruktur, akan menampilkan instancePerson. extractPerson()dianotasi dengan anotasi@SystemMessageyang mengaitkan perintah petunjuk dengannya. Itulah perintah yang akan digunakan model untuk memandu ekstraksi informasi, dan menampilkan detail dalam bentuk dokumen JSON, yang akan diuraikan untuk Anda, dan di-unmarshal ke dalam instancePerson.
Sekarang, mari kita lihat konten metode main():
- Model chat dikonfigurasi dan di-instantiate. Kita menggunakan 2 metode baru dari class model builder:
responseMimeType()danresponseSchema(). Yang pertama memberi tahu Gemini untuk menghasilkan JSON yang valid dalam output. Metode kedua menentukan skema objek JSON yang harus ditampilkan. Selain itu, yang terakhir mendelegasikan ke metode praktis yang dapat mengonversi class atau rekaman Java menjadi skema JSON yang tepat. - Objek
PersonExtractordibuat berkat classAiServicesLangChain4j. - Kemudian, Anda cukup memanggil
Person person = extractor.extractPerson(...)untuk mengekstrak detail orang tersebut dari teks tidak terstruktur, dan mendapatkan kembali instancePersondengan nama dan usia.
Jalankan contoh:
./gradlew run -q -DjavaMainClass=gemini.workshop.ExtractData
Anda akan melihat output berikut:
Anna 23
Ya, ini Anna dan dia berusia 23 tahun.
Dengan pendekatan AiServices ini, Anda beroperasi dengan objek yang sangat diketik. Anda tidak berinteraksi langsung dengan LLM. Sebagai gantinya, Anda bekerja dengan class konkret, seperti rekaman Person untuk merepresentasikan informasi pribadi yang diekstrak, dan Anda memiliki objek PersonExtractor dengan metode extractPerson() yang menampilkan instance Person. Konsep LLM diabaikan, dan sebagai developer Java, Anda hanya memanipulasi class dan objek normal saat menggunakan antarmuka PersonExtractor ini.
8. Menyusun perintah dengan template perintah
Saat Anda berinteraksi dengan LLM menggunakan serangkaian petunjuk atau pertanyaan umum, ada bagian perintah yang tidak pernah berubah, sementara bagian lainnya berisi data. Misalnya, jika Anda ingin membuat resep, Anda dapat menggunakan perintah seperti "Anda adalah koki berbakat, buat resep dengan bahan-bahan berikut: ...", lalu Anda akan menambahkan bahan-bahan di akhir teks tersebut. Itulah fungsi template perintah — mirip dengan string yang diinterpolasi dalam bahasa pemrograman. Template perintah berisi placeholder yang dapat Anda ganti dengan data yang tepat untuk panggilan tertentu ke LLM.
Lebih konkretnya, mari kita pelajari TemplatePrompt.java di direktori 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());
}
}
Seperti biasa, Anda mengonfigurasi model VertexAiGeminiChatModel, dengan tingkat kreativitas yang tinggi dengan suhu yang tinggi serta nilai topP dan topK yang tinggi. Kemudian, Anda membuat PromptTemplate dengan metode statis from(), dengan meneruskan string perintah, dan menggunakan variabel placeholder kurung kurawal ganda: {{dish}} dan {{ingredients}}.
Anda membuat perintah akhir dengan memanggil apply() yang menggunakan peta key/value pair yang merepresentasikan nama placeholder dan nilai string untuk menggantikannya.
Terakhir, Anda memanggil metode generate() model Gemini dengan membuat pesan pengguna dari perintah tersebut, dengan instruksi prompt.toUserMessage().
Jalankan contoh:
./gradlew run -q -DjavaMainClass=gemini.workshop.TemplatePrompt
Anda akan melihat output yang dihasilkan dan terlihat mirip dengan yang berikut ini:
**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.
Jangan ragu untuk mengubah nilai dish dan ingredients dalam peta serta menyesuaikan suhu, topK dan tokP, lalu jalankan kembali kode. Dengan begitu, Anda dapat mengamati efek perubahan parameter ini pada LLM.
Template perintah adalah cara yang baik untuk memiliki petunjuk yang dapat digunakan kembali dan dapat diparameterkan untuk panggilan LLM. Anda dapat meneruskan data dan menyesuaikan perintah untuk berbagai nilai yang diberikan oleh pengguna.
9. Klasifikasi teks dengan few-shot prompting
LLM cukup baik dalam mengklasifikasikan teks ke dalam berbagai kategori. Anda dapat membantu LLM dalam tugas tersebut dengan memberikan beberapa contoh teks dan kategori terkaitnya. Pendekatan ini sering disebut sebagai few shot prompting.
Mari buka TextClassification.java di direktori app/src/main/java/gemini/workshop, untuk melakukan jenis klasifikasi teks tertentu: analisis sentimen.
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!"));
}
}
Enum Sentiment mencantumkan berbagai nilai untuk sentimen: negatif, netral, atau positif.
Dalam metode main(), Anda membuat model chat Gemini seperti biasa, tetapi dengan jumlah token output maksimum yang kecil, karena Anda hanya menginginkan respons singkat: teksnya adalah POSITIVE, NEGATIVE, atau NEUTRAL. Untuk membatasi model agar hanya menampilkan nilai tersebut, secara eksklusif, Anda dapat memanfaatkan dukungan output terstruktur yang Anda temukan di bagian ekstraksi data. Itulah sebabnya metode responseSchema() digunakan. Kali ini, Anda tidak menggunakan metode praktis dari SchemaHelper untuk menyimpulkan definisi skema, tetapi Anda akan menggunakan builder Schema untuk memahami seperti apa definisi skema.
Setelah model dikonfigurasi, Anda membuat antarmuka SentimentAnalysis yang akan diimplementasikan oleh AiServices LangChain4j untuk Anda menggunakan LLM. Antarmuka ini berisi satu metode: analyze(). Metode ini menggunakan teks yang akan dianalisis sebagai input, dan menampilkan nilai enum Sentiment. Jadi, Anda hanya memanipulasi objek berjenis kuat yang merepresentasikan class sentimen yang dikenali.
Kemudian, untuk memberikan "contoh beberapa shot" guna mendorong model melakukan tugas klasifikasinya, Anda membuat memori chat untuk meneruskan pasangan pesan pengguna dan respons AI yang merepresentasikan teks dan sentimen yang terkait dengannya.
Mari kita gabungkan semuanya dengan metode AiServices.builder(), dengan meneruskan antarmuka SentimentAnalysis, model yang akan digunakan, dan memori chat dengan contoh few-shot. Terakhir, panggil metode analyze() dengan teks yang akan dianalisis.
Jalankan contoh:
./gradlew run -q -DjavaMainClass=gemini.workshop.TextClassification
Anda akan melihat satu kata:
POSITIVE
Sepertinya menyukai stroberi adalah sentimen positif.
10. Retrieval-Augmented Generation
LLM dilatih dengan sejumlah besar teks. Namun, pengetahuannya hanya mencakup informasi yang telah dilihatnya selama pelatihan. Jika ada informasi baru yang dirilis setelah tanggal batas pelatihan model, detail tersebut tidak akan tersedia untuk model. Oleh karena itu, model tidak akan dapat menjawab pertanyaan tentang informasi yang belum pernah dilihatnya.
Itulah sebabnya pendekatan seperti Retrieval-Augmented Generation (RAG) yang akan dibahas di bagian ini membantu memberikan informasi tambahan yang mungkin perlu diketahui LLM untuk memenuhi permintaan penggunanya, untuk membalas dengan informasi yang mungkin lebih baru atau tentang informasi pribadi yang tidak dapat diakses pada saat pelatihan.
Mari kembali ke percakapan. Kali ini, Anda dapat mengajukan pertanyaan tentang dokumen Anda. Anda akan membuat chatbot yang dapat mengambil informasi yang relevan dari database yang berisi dokumen Anda yang dibagi menjadi bagian-bagian yang lebih kecil ("chunk") dan informasi tersebut akan digunakan oleh model untuk mendasari jawabannya, alih-alih hanya mengandalkan pengetahuan yang terkandung dalam pelatihannya.
Dalam RAG, ada dua fase:
- Fase penyerapan — Dokumen dimuat dalam memori, dibagi menjadi potongan yang lebih kecil, dan embedding vektor (representasi vektor multidimensi tinggi dari potongan) dihitung dan disimpan dalam database vektor yang mampu melakukan penelusuran semantik. Fase penyerapan ini biasanya dilakukan satu kali, saat dokumen baru perlu ditambahkan ke korpus dokumen.

- Fase kueri — Pengguna kini dapat mengajukan pertanyaan tentang dokumen. Pertanyaan juga akan diubah menjadi vektor dan dibandingkan dengan semua vektor lain dalam database. Vektor yang paling mirip biasanya terkait secara semantik dan ditampilkan oleh database vektor. Kemudian, LLM diberi konteks percakapan, potongan teks yang sesuai dengan vektor yang ditampilkan oleh database, dan diminta untuk mendasarkan jawabannya dengan melihat potongan tersebut.

Menyiapkan dokumen Anda
Untuk contoh baru ini, Anda akan mengajukan pertanyaan tentang model mobil fiktif dari produsen mobil yang juga fiktif: mobil Cymbal Starlight. Idenya adalah bahwa dokumen tentang mobil fiktif tidak boleh menjadi bagian dari pengetahuan model. Jadi, jika Gemini dapat menjawab pertanyaan tentang mobil ini dengan benar, berarti pendekatan RAG berhasil: Gemini dapat menelusuri dokumen Anda.
Menerapkan chatbot
Mari kita pelajari cara membangun pendekatan 2 fase: pertama dengan penyerapan dokumen, lalu waktu kueri (juga disebut "fase pengambilan") saat pengguna mengajukan pertanyaan tentang dokumen.
Dalam contoh ini, kedua fase diimplementasikan dalam class yang sama. Biasanya, Anda akan memiliki satu aplikasi yang menangani penyerapan, dan aplikasi lain yang menawarkan antarmuka chatbot kepada pengguna Anda.
Selain itu, dalam contoh ini kita akan menggunakan database vektor dalam memori. Dalam skenario produksi yang sebenarnya, fase penyerapan dan kueri akan dipisahkan dalam dua aplikasi yang berbeda, dan vektor dipertahankan dalam database mandiri.
Penyerapan dokumen
Langkah pertama dalam fase penyerapan dokumen adalah menemukan file PDF tentang mobil fiktif kami, dan menyiapkan PdfParser untuk membacanya:
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());
Daripada membuat model bahasa chat biasa terlebih dahulu, Anda membuat instance model embedding. Model ini adalah model khusus yang berperan untuk membuat representasi vektor dari potongan teks (kata, kalimat, atau bahkan paragraf). Model ini menampilkan vektor bilangan floating point, bukan menampilkan respons teks.
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();
Selanjutnya, Anda memerlukan beberapa kelas untuk berkolaborasi bersama untuk:
- Muat dan pisahkan dokumen PDF menjadi beberapa bagian.
- Buat embedding vektor untuk semua potongan ini.
InMemoryEmbeddingStore<TextSegment> embeddingStore =
new InMemoryEmbeddingStore<>();
EmbeddingStoreIngestor storeIngestor = EmbeddingStoreIngestor.builder()
.documentSplitter(DocumentSplitters.recursive(500, 100))
.embeddingModel(embeddingModel)
.embeddingStore(embeddingStore)
.build();
storeIngestor.ingest(document);
Instance InMemoryEmbeddingStore, database vektor dalam memori, dibuat untuk menyimpan embedding vektor.
Dokumen dibagi menjadi beberapa bagian berkat class DocumentSplitters. Teks file PDF akan dibagi menjadi cuplikan 500 karakter, dengan tumpang-tindih 100 karakter (dengan potongan berikutnya, untuk menghindari pemotongan kata atau kalimat, menjadi beberapa bagian).
Pengimpor toko menautkan pemisah dokumen, model embedding untuk menghitung vektor, dan database vektor dalam memori. Kemudian, metode ingest() akan menangani penyerapan.
Sekarang, fase pertama telah selesai, dokumen telah diubah menjadi potongan teks dengan embedding vektor terkait, dan disimpan dalam database vektor.
Mengajukan pertanyaan
Saatnya bersiap untuk mengajukan pertanyaan. Buat model chat untuk memulai percakapan:
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.maxOutputTokens(1000)
.build();
Anda juga memerlukan class retriever untuk menautkan database vektor (dalam variabel embeddingStore) dengan model embedding. Tugasnya adalah membuat kueri database vektor dengan menghitung embedding vektor untuk kueri pengguna, guna menemukan vektor serupa dalam database:
EmbeddingStoreContentRetriever retriever =
new EmbeddingStoreContentRetriever(embeddingStore, embeddingModel);
Buat antarmuka yang merepresentasikan asisten pakar mobil, yaitu antarmuka yang akan diimplementasikan oleh class AiServices agar Anda dapat berinteraksi dengan model:
interface CarExpert {
Result<String> ask(String question);
}
Antarmuka CarExpert menampilkan respons string yang di-wrap dalam class Result LangChain4j. Mengapa menggunakan wrapper ini? Karena tidak hanya akan memberikan jawaban, tetapi juga memungkinkan Anda memeriksa potongan dari database yang telah ditampilkan oleh pengambil konten. Dengan begitu, Anda dapat menampilkan sumber dokumen yang digunakan untuk mendasari jawaban akhir kepada pengguna.
Pada tahap ini, Anda dapat mengonfigurasi layanan AI baru:
CarExpert expert = AiServices.builder(CarExpert.class)
.chatLanguageModel(model)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.contentRetriever(retriever)
.build();
Layanan ini mengikat:
- Model bahasa chat yang Anda konfigurasi sebelumnya.
- Memori chat untuk melacak percakapan.
- Pengambil membandingkan kueri embedding vektor dengan vektor dalam database.
.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())
Anda akhirnya siap mengajukan pertanyaan.
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());
});
Kode sumber lengkap ada di RAG.java di direktori app/src/main/java/gemini/workshop.
Jalankan contoh:
./gradlew -q run -DjavaMainClass=gemini.workshop.RAG
Dalam output, Anda akan melihat jawaban atas pertanyaan Anda:
=== 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. Panggilan fungsi
Ada situasi ketika Anda ingin LLM memiliki akses ke sistem eksternal, seperti API web jarak jauh yang mengambil informasi atau memiliki tindakan, atau layanan yang melakukan semacam komputasi. Contoh:
API web jarak jauh:
- Melacak dan memperbarui pesanan pelanggan.
- Temukan atau buat tiket di pelacak masalah.
- Mengambil data real time seperti kuotasi saham atau pengukuran sensor IoT.
- Mengirim email.
Alat komputasi:
- Kalkulator untuk soal matematika yang lebih rumit.
- Interpretasi kode untuk menjalankan kode saat LLM memerlukan logika penalaran.
- Mengonversi permintaan bahasa alami menjadi kueri SQL sehingga LLM dapat mengkueri database.
Panggilan fungsi (terkadang disebut alat, atau penggunaan alat) adalah kemampuan model untuk meminta satu atau beberapa panggilan fungsi dilakukan atas namanya, sehingga model dapat menjawab perintah pengguna dengan data yang lebih baru.
Dengan perintah tertentu dari pengguna, dan pengetahuan tentang fungsi yang ada yang dapat relevan dengan konteks tersebut, LLM dapat membalas dengan permintaan panggilan fungsi. Aplikasi yang mengintegrasikan LLM kemudian dapat memanggil fungsi atas namanya, lalu membalas ke LLM dengan respons, dan LLM kemudian menafsirkan kembali dengan membalas menggunakan jawaban tekstual.
Empat langkah panggilan fungsi
Mari kita lihat contoh panggilan fungsi: mendapatkan informasi tentang prakiraan cuaca.
Jika Anda bertanya kepada Gemini atau LLM lain tentang cuaca di Paris, mereka akan menjawab dengan mengatakan bahwa mereka tidak memiliki informasi tentang prakiraan cuaca saat ini. Jika Anda ingin LLM memiliki akses real-time ke data cuaca, Anda perlu menentukan beberapa fungsi yang dapat diminta untuk digunakan.
Lihat diagram berikut:

1️⃣ Pertama, pengguna bertanya tentang cuaca di Paris. Aplikasi chatbot (menggunakan LangChain4j) mengetahui bahwa ada satu atau beberapa fungsi yang dapat digunakannya untuk membantu LLM memenuhi kueri. Chatbot mengirimkan perintah awal, serta daftar fungsi yang dapat dipanggil. Di sini, fungsi yang disebut getWeather() menggunakan parameter string untuk lokasi.

Karena LLM tidak mengetahui perkiraan cuaca, LLM mengirimkan kembali permintaan eksekusi fungsi, bukan membalas melalui teks. Chatbot harus memanggil fungsi getWeather() dengan "Paris" sebagai parameter lokasi.
2️⃣ Chatbot memanggil fungsi tersebut atas nama LLM, mengambil respons fungsi. Di sini, kita membayangkan bahwa responsnya adalah {"forecast": "sunny"}.

3️⃣ Aplikasi chatbot mengirimkan respons JSON kembali ke LLM.

4️⃣ LLM melihat respons JSON, menafsirkan informasi tersebut, dan akhirnya membalas dengan teks bahwa cuaca di Paris sedang cerah.

Setiap langkah sebagai kode
Pertama, Anda akan mengonfigurasi model Gemini seperti biasa:
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.maxOutputTokens(100)
.build();
Anda menentukan spesifikasi alat yang menjelaskan fungsi yang dapat dipanggil:
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();
Nama fungsi ditentukan, serta nama dan jenis parameter, tetapi perhatikan bahwa fungsi dan parameter diberi deskripsi. Deskripsi sangat penting dan membantu LLM benar-benar memahami apa yang dapat dilakukan suatu fungsi, sehingga dapat menilai apakah fungsi ini perlu dipanggil dalam konteks percakapan.
Mari kita mulai langkah #1, dengan mengirimkan pertanyaan awal tentang cuaca di Paris:
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);
Pada langkah #2, kita meneruskan alat yang ingin kita gunakan oleh model, dan model akan membalas dengan permintaan eksekusi alat:
// 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());
Langkah #3. Pada tahap ini, kita mengetahui fungsi yang ingin dipanggil LLM. Dalam kode, kita tidak melakukan panggilan nyata ke API eksternal, kita hanya menampilkan perkiraan cuaca hipotetis secara langsung:
// 3) We send back the result of the function call
ToolExecutionResultMessage toolExecResMsg = ToolExecutionResultMessage.from(toolExecutionRequest,
"{\"location\":\"Paris\",\"forecast\":\"sunny\", \"temperature\": 20}");
allMessages.add(toolExecResMsg);
Pada langkah #4, LLM mempelajari hasil eksekusi fungsi, lalu dapat menyintesis respons tekstual:
// 4) The model answers with a sentence describing the weather
Response<AiMessage> weatherResponse = model.generate(allMessages);
System.out.println("Answer: " + weatherResponse.content().text());
Kode sumber lengkap ada di FunctionCalling.java di direktori app/src/main/java/gemini/workshop.
Jalankan contoh:
./gradlew run -q -DjavaMainClass=gemini.workshop.FunctionCalling
Anda akan melihat output yang mirip dengan berikut ini:
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.
Anda dapat melihat permintaan eksekusi alat, serta jawabannya, dalam output di atas.
12. LangChain4j menangani panggilan fungsi
Pada langkah sebelumnya, Anda telah melihat bagaimana interaksi pertanyaan/jawaban teks normal dan permintaan/respons fungsi diselingi, dan di antaranya, Anda memberikan respons fungsi yang diminta secara langsung, tanpa memanggil fungsi yang sebenarnya.
Namun, LangChain4j juga menawarkan abstraksi tingkat yang lebih tinggi yang dapat menangani panggilan fungsi secara transparan untuk Anda, sekaligus menangani percakapan seperti biasa.
Panggilan fungsi tunggal
Mari kita lihat FunctionCallingAssistant.java, bagian demi bagian.
Pertama, Anda membuat rekaman yang akan merepresentasikan struktur data respons fungsi:
record WeatherForecast(String location, String forecast, int temperature) {}
Respons berisi informasi tentang lokasi, perkiraan cuaca, dan suhu.
Kemudian, Anda membuat class yang berisi fungsi sebenarnya yang ingin Anda sediakan untuk model:
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);
}
}
}
Perhatikan bahwa class ini berisi satu fungsi, tetapi dianotasi dengan anotasi @Tool yang sesuai dengan deskripsi fungsi yang dapat diminta oleh model untuk dipanggil.
Parameter fungsi (di sini hanya ada satu) juga dianotasi, tetapi dengan anotasi @P singkat ini, yang juga memberikan deskripsi parameter. Anda dapat menambahkan fungsi sebanyak yang Anda inginkan, agar tersedia untuk model, untuk skenario yang lebih kompleks.
Dalam class ini, Anda menampilkan beberapa respons standar, tetapi jika ingin memanggil layanan prakiraan cuaca eksternal yang sebenarnya, Anda akan melakukan panggilan ke layanan tersebut di isi metode ini.
Seperti yang kita lihat saat Anda membuat ToolSpecification dalam pendekatan sebelumnya, penting untuk mendokumentasikan fungsi yang dilakukan, dan menjelaskan korespondensi parameter. Hal ini membantu model memahami cara dan waktu penggunaan fungsi ini.
Selanjutnya, LangChain4j memungkinkan Anda menyediakan antarmuka yang sesuai dengan kontrak yang ingin Anda gunakan untuk berinteraksi dengan model. Di sini, antarmukanya sederhana yang menerima string yang merepresentasikan pesan pengguna, dan menampilkan string yang sesuai dengan respons model:
interface WeatherAssistant {
String chat(String userMessage);
}
Anda juga dapat menggunakan tanda tangan yang lebih kompleks yang melibatkan UserMessage LangChain4j (untuk pesan pengguna) atau AiMessage (untuk respons model), atau bahkan TokenStream, jika Anda ingin menangani situasi yang lebih rumit, karena objek yang lebih rumit tersebut juga berisi informasi tambahan seperti jumlah token yang digunakan, dll. Namun, demi kesederhanaan, kita hanya akan mengambil string sebagai input, dan string sebagai output.
Mari kita selesaikan dengan metode main() yang mengikat semua bagian:
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?"));
}
Seperti biasa, Anda mengonfigurasi model percakapan Gemini. Kemudian, Anda membuat instance layanan prakiraan cuaca yang berisi "fungsi" yang akan diminta model untuk dipanggil.
Sekarang, Anda menggunakan class AiServices lagi untuk mengikat model chat, memori chat, dan alat (yaitu layanan prakiraan cuaca dengan fungsinya). AiServices menampilkan objek yang menerapkan antarmuka WeatherAssistant yang Anda tentukan. Satu-satunya hal yang tersisa adalah memanggil metode chat() asisten tersebut. Saat memanggilnya, Anda hanya akan melihat respons teks, tetapi permintaan panggilan fungsi dan respons panggilan fungsi tidak akan terlihat oleh developer, dan permintaan tersebut akan ditangani secara otomatis dan transparan. Jika Gemini berpikir bahwa suatu fungsi harus dipanggil, Gemini akan membalas dengan permintaan panggilan fungsi, dan LangChain4j akan menangani pemanggilan fungsi lokal atas nama Anda.
Jalankan contoh:
./gradlew run -q -DjavaMainClass=gemini.workshop.FunctionCallingAssistant
Anda akan melihat output yang mirip dengan berikut ini:
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).
Ini adalah contoh fungsi tunggal.
Beberapa panggilan fungsi
Anda juga dapat memiliki beberapa fungsi dan membiarkan LangChain4j menangani beberapa panggilan fungsi atas nama Anda. Lihat MultiFunctionCallingAssistant.java untuk contoh beberapa fungsi.
API ini memiliki fungsi untuk mengonversi mata uang:
@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;
}
Fungsi lain untuk mendapatkan nilai saham:
@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;
}
Fungsi lain untuk menerapkan persentase ke jumlah tertentu:
@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;
}
Kemudian, Anda dapat menggabungkan semua fungsi ini dan class MultiTools, lalu mengajukan pertanyaan seperti "Berapa 10% dari harga saham AAPL yang dikonversi dari USD ke 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?"));
}
Jalankan seperti berikut:
./gradlew run -q -DjavaMainClass=gemini.workshop.MultiFunctionCallingAssistant
Anda akan melihat beberapa fungsi yang dipanggil:
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.
Menuju Agen
Pemanggilan fungsi adalah mekanisme ekstensi yang sangat baik untuk model bahasa besar seperti Gemini. Dengan demikian, kita dapat membangun sistem yang lebih kompleks yang sering disebut "agen" atau "asisten AI". Agen ini dapat berinteraksi dengan dunia luar melalui API eksternal dan dengan layanan yang dapat menimbulkan efek samping pada lingkungan eksternal (seperti mengirim email, membuat tiket, dll.)
Saat membuat agen yang canggih seperti itu, Anda harus melakukannya secara bertanggung jawab. Anda harus mempertimbangkan penggunaan manusia dalam proses sebelum melakukan tindakan otomatis. Penting untuk mempertimbangkan keamanan saat mendesain agen yang didukung LLM yang berinteraksi dengan dunia luar.
13. Menjalankan Gemma dengan Ollama dan TestContainers
Sejauh ini, kita telah menggunakan Gemini, tetapi ada juga Gemma, model yang lebih kecil.
Gemma adalah sekumpulan model open source yang ringan dan canggih, dibangun dari riset dan teknologi yang sama dengan yang digunakan untuk membuat model Gemini. Model Gemma terbaru adalah Gemma3 yang tersedia dalam empat ukuran: 1B (khusus teks), 4B, 12B, dan 27B. Bobotnya tersedia secara bebas, dan ukurannya yang kecil berarti Anda dapat menjalankannya sendiri, bahkan di laptop atau di Cloud Shell.
Bagaimana cara menjalankan Gemma?
Ada banyak cara untuk menjalankan Gemma: di cloud, melalui Vertex AI dengan sekali klik, atau GKE dengan beberapa GPU, tetapi Anda juga dapat menjalankannya secara lokal.
Salah satu opsi yang baik untuk menjalankan Gemma secara lokal adalah dengan Ollama, alat yang memungkinkan Anda menjalankan model kecil, seperti Llama, Mistral, dan banyak model lainnya di komputer lokal Anda. Mirip dengan Docker, tetapi untuk LLM.
Instal Ollama dengan mengikuti petunjuk untuk Sistem Operasi Anda.
Jika menggunakan lingkungan Linux, Anda harus mengaktifkan Ollama terlebih dahulu setelah menginstalnya.
ollama serve > /dev/null 2>&1 &
Setelah diinstal secara lokal, Anda dapat menjalankan perintah untuk menarik model:
ollama pull gemma3:1b
Tunggu hingga model ditarik. Proses ini dapat memerlukan waktu agak lama.
Jalankan model:
ollama run gemma3:1b
Sekarang, Anda dapat berinteraksi dengan model:
>>> Hello! Hello! It's nice to hear from you. What can I do for you today?
Untuk keluar dari perintah, tekan Ctrl+D
Menjalankan Gemma di Ollama di TestContainers
Daripada harus menginstal dan menjalankan Ollama secara lokal, Anda dapat menggunakan Ollama dalam container, yang ditangani oleh TestContainers.
TestContainers tidak hanya berguna untuk pengujian, tetapi Anda juga dapat menggunakannya untuk mengeksekusi container. Bahkan ada OllamaContainer khusus yang dapat Anda manfaatkan.
Berikut gambaran lengkapnya:

Penerapan
Mari kita lihat GemmaWithOllamaContainer.java, bagian demi bagian.
Pertama, Anda perlu membuat container Ollama turunan yang menarik model Gemma. Image ini sudah ada dari proses sebelumnya atau akan dibuat. Jika image sudah ada, Anda hanya perlu memberi tahu TestContainers bahwa Anda ingin mengganti image Ollama default dengan varian yang didukung 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"));
}
Selanjutnya, Anda membuat dan memulai container pengujian Ollama, lalu membuat model chat Ollama dengan mengarahkan ke alamat dan port container dengan model yang ingin Anda gunakan. Terakhir, Anda cukup memanggil model.generate(yourPrompt) seperti biasa:
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);
}
Jalankan seperti berikut:
./gradlew run -q -DjavaMainClass=gemini.workshop.GemmaWithOllamaContainer
Penayangan pertama akan memerlukan waktu beberapa saat untuk membuat dan menjalankan penampung, tetapi setelah selesai, Anda akan melihat Gemma merespons:
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.
Anda telah menjalankan Gemma di Cloud Shell.
14. Selamat
Selamat, Anda telah berhasil membangun aplikasi chat AI Generatif pertama Anda di Java menggunakan LangChain4j dan Gemini API. Anda menemukan bahwa model bahasa besar multimodal cukup canggih dan mampu menangani berbagai tugas seperti tanya jawab, bahkan pada dokumentasi Anda sendiri, ekstraksi data, berinteraksi dengan API eksternal, dan banyak lagi.
Apa selanjutnya?
Sekarang giliran Anda untuk meningkatkan kualitas aplikasi dengan integrasi LLM yang canggih.
Bacaan lebih lanjut
- Kasus penggunaan umum AI generatif
- Materi pelatihan tentang AI Generatif
- Berinteraksi dengan Gemini melalui Generative AI Studio
- Responsible AI