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, memunculkan ide, mengekstrak konten terstruktur dan entitas, membuat retrieval augmented generation, serta melakukan panggilan fungsi.
Apa yang dimaksud dengan 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 pembuatan ringkasan, tanya jawab, klasifikasi, dan sebagainya. Dengan sedikit pelatihan, model dasar dapat diadaptasikan untuk kasus penggunaan tertarget 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 berisi 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 ini diberi serangkaian konten buatan manusia dan label yang sesuai. AI generatif kemudian belajar membuat konten yang serupa dengan konten buatan manusia tersebut.
Apa saja aplikasi AI Generatif yang umum digunakan?
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 ringkasan.
- 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, menyesuaikan model tersebut, dan menyematkannya ke aplikasi Anda dengan sedikit atau tanpa keahlian ML. Anda dapat mengakses model dasar di Model Garden, menyesuaikan berbagai model melalui UI yang sederhana di Vertex AI Studio, atau menggunakan model dalam notebook data science.
Vertex AI Search and Conversation menawarkan cara tercepat bagi 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 tugas dengan lebih cepat. Gemini Code Assist menyediakan penyelesaian kode, pembuatan kode, penjelasan kode, dan memungkinkan Anda melakukan chat untuk mengajukan pertanyaan teknis.
Apa itu Gemini?
Gemini adalah serangkaian 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 tersedia dalam berbagai variasi dan ukuran:
- Gemini Ultra: Versi terbesar dan tercanggih untuk tugas kompleks.
- Gemini Flash: Tercepat dan paling hemat biaya, dioptimalkan untuk tugas bervolume tinggi.
- Gemini Pro: Berukuran sedang, dioptimalkan untuk penskalaan di berbagai tugas.
- Gemini Nano: Model paling efisien, dirancang untuk tugas di perangkat.
Fitur Utama:
- Multimodalitas: Kemampuan Gemini untuk memahami dan menangani beberapa format informasi adalah langkah signifikan di luar model bahasa khusus teks tradisional.
- Performa: Gemini Ultra mengungguli teknologi terbaru saat ini di banyak benchmark dan merupakan model pertama yang melampaui pakar manusia dalam benchmark 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?
Ada dua opsi:
- Library Vertex AI Java API untuk Gemini resmi.
- Framework LangChain4j.
Dalam codelab ini, Anda akan menggunakan framework LangChain4j.
Apa yang dimaksud dengan framework LangChain4j?
Framework LangChain4j adalah library open source untuk mengintegrasikan LLM dalam aplikasi Java Anda, dengan mengatur berbagai komponen, seperti LLM itu sendiri, tetapi juga alat lain seperti database vektor (untuk penelusuran semantik), loader dan pemisah 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 ke Gemini secara terprogram
- Cara menstreaming respons dari Gemini
- Cara membuat percakapan antara pengguna dan Gemini
- Cara menggunakan Gemini dalam konteks multimodal dengan mengirim 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 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 Lanjutkan.
Perlu waktu beberapa saat untuk penyediaan dan terhubung ke Cloud Shell.
Virtual machine ini berisi 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 Project Selector, lalu pilih project yang diinginkan.
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 (misalnya, /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-1.5-flash-002")
.build();
System.out.println(model.generate("Why is the sky blue?"));
}
}
Dalam contoh pertama ini, Anda perlu mengimpor class VertexAiGeminiChatModel
, yang mengimplementasikan antarmuka ChatModel
.
Dalam metode main
, Anda mengonfigurasi model bahasa chat menggunakan builder untuk VertexAiGeminiChatModel
dan menentukan:
- Project
- Lokasi
- Nama model (
gemini-1.5-flash-002
).
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 melihat bahwa respons diberikan sekaligus, setelah beberapa detik? Anda juga dapat mendapatkan respons secara bertahap, berkat varian respons streaming. Respons streaming, model menampilkan respons secara bertahap, saat tersedia.
Dalam codelab ini, kita akan tetap 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-1.5-flash-002")
.maxOutputTokens(4000)
.build();
model.generate("Why is the sky blue?", onNext(System.out::println));
}
}
Kali ini, kita akan mengimpor varian class streaming VertexAiGeminiStreamingChatModel
yang mengimplementasikan antarmuka StreamingChatLanguageModel
. Anda juga harus mengimpor LambdaStreamingResponseHandler.onNext
secara statis yang merupakan metode praktis yang menyediakan StreamingResponseHandler
untuk membuat pengendali streaming dengan ekspresi lambda Java.
Kali ini, tanda tangan metode generate()
sedikit berbeda. Jenis nilai yang ditampilkan adalah void, bukan string. Selain perintah, Anda harus meneruskan pengendali respons streaming. Di sini, berkat impor statis yang kita sebutkan di atas, kita dapat menentukan ekspresi lambda yang Anda teruskan ke metode onNext()
. Ekspresi lambda dipanggil setiap kali bagian baru respons tersedia, sedangkan yang terakhir hanya dipanggil jika terjadi error.
Jalankan:
./gradlew run -q -DjavaMainClass=gemini.workshop.StreamQA
Anda akan mendapatkan jawaban yang serupa dengan class sebelumnya, tetapi kali ini, Anda akan melihat bahwa jawaban muncul secara bertahap 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 respons yang kurang kreatif dan sering kali lebih faktual, sedangkan 2 berarti respons yang lebih kreatif)topP(Float topP)
— untuk memilih kemungkinan kata yang total probabilitasnya berjumlah sama dengan bilangan 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 melebihi kuota permintaan per waktu, atau platform mengalami beberapa masalah teknis, Anda dapat meminta model untuk mencoba ulang panggilan 3 kali
Sejauh ini, Anda telah mengajukan satu pertanyaan kepada Gemini, tetapi Anda juga dapat melakukan percakapan multi-giliran. Hal 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 sebenarnya.
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-1.5-flash-002")
.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 class ini:
MessageWindowChatMemory
— class yang akan membantu menangani aspek multi-giliran percakapan, dan menyimpan pertanyaan dan jawaban sebelumnya di memori lokalAiServices
— class abstraksi tingkat tinggi yang akan menyatukan model chat dan memori chat
Dalam metode utama, Anda akan menyiapkan model, memori chat, dan layanan AI. Model dikonfigurasi seperti biasa dengan informasi nama project, lokasi, dan model.
Untuk memori chat, kita menggunakan builder MessageWindowChatMemory
untuk membuat memori yang menyimpan 20 pesan terakhir yang dipertukarkan. Ini adalah jendela geser pada percakapan yang konteksnya disimpan secara lokal di klien class Java kita.
Kemudian, Anda membuat AI service
yang mengikat model chat dengan memori chat.
Perhatikan cara layanan AI menggunakan antarmuka ConversationService
kustom yang telah kita tentukan, yang diterapkan LangChain4j, dan yang mengambil kueri String
dan menampilkan respons String
.
Sekarang, saatnya untuk melakukan percakapan dengan Gemini. Pertama, ucapan sederhana dikirim, lalu pertanyaan pertama tentang Menara Eiffel untuk mengetahui di negara mana Menara Eiffel berada. Perhatikan bahwa kalimat terakhir terkait dengan jawaban pertanyaan pertama, karena Anda ingin tahu jumlah penduduk di negara tempat Menara Eiffel berada, tanpa menyebutkan secara eksplisit negara yang diberikan dalam jawaban sebelumnya. 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 di langkah berikutnya.
6. Multimodalitas dengan Gemini
Gemini adalah model multimodal. Model ini 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 Wikipediahttps://upload.wikimedia.org/wikipedia/commons/b/b6/Felis_catus-cat_on_snow.jpg
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-1.5-flash-002")
.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
. Ini adalah multimodalitas yang diterapkan: mencampur teks dan gambar. Kita tidak hanya mengirim perintah string sederhana, tetapi juga mengirim objek yang lebih terstruktur yang mewakili 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 tentu memberi Anda petunjuk tentang isi gambar, tetapi output Gemini mirip dengan berikut ini:
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 akan 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 saat informasi penting diberikan dalam dokumen laporan, email, atau teks panjang lainnya dengan cara yang tidak terstruktur. Idealnya, Anda ingin dapat mengekstrak detail utama yang terdapat dalam teks tidak terstruktur, dalam bentuk objek terstruktur. Mari kita lihat cara melakukannya.
Misalnya, Anda ingin mengekstrak nama dan usia seseorang, dengan biografi, CV, atau deskripsi orang tersebut. Anda dapat menginstruksikan LLM untuk mengekstrak JSON dari teks tidak terstruktur dengan perintah yang dioptimalkan dengan cerdik (biasanya disebut "prompt engineering").
Namun, dalam contoh di bawah, daripada membuat perintah yang menjelaskan output JSON, kita akan menggunakan fitur Gemini yang canggih yang disebut output terstruktur, atau terkadang decoding terbatas, 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-1.5-flash-002")
.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
Person
ditentukan untuk mewakili detail yang menjelaskan seseorang (nama dan usia). - Antarmuka
PersonExtractor
ditentukan dengan metode yang, dengan string teks tidak terstruktur, menampilkan instancePerson
. extractPerson()
dianotasi dengan anotasi@SystemMessage
yang mengaitkan perintah petunjuk dengannya. Ini adalah perintah yang akan digunakan model untuk memandu ekstraksi informasinya, dan menampilkan detail dalam bentuk dokumen JSON, yang akan diuraikan untuk Anda, dan diunmarshal ke dalam instancePerson
.
Sekarang, mari kita lihat konten metode main()
:
- Model chat dikonfigurasi dan dibuat instance-nya. Kita menggunakan 2 metode baru dari class builder model:
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, metode kedua mendelegasikan ke metode praktis yang dapat mengonversi class atau data Java menjadi skema JSON yang tepat. - Objek
PersonExtractor
dibuat berkat classAiServices
LangChain4j. - Kemudian, Anda cukup memanggil
Person person = extractor.extractPerson(...)
untuk mengekstrak detail orang dari teks tidak terstruktur, dan mendapatkan kembali instancePerson
dengan 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 dengan jenis yang kuat. Anda tidak berinteraksi langsung dengan LLM. Sebagai gantinya, Anda menggunakan class konkret, seperti data Person
untuk merepresentasikan informasi pribadi yang diekstrak, dan Anda memiliki objek PersonExtractor
dengan metode extractPerson()
yang menampilkan instance Person
. Istilah LLM di-abstract, dan sebagai developer Java, Anda hanya memanipulasi class dan objek normal, saat menggunakan antarmuka PersonExtractor
ini.
8. Membuat struktur perintah dengan template perintah
Saat Anda berinteraksi dengan LLM menggunakan kumpulan petunjuk atau pertanyaan umum, ada bagian dari perintah tersebut yang tidak pernah berubah, sedangkan bagian lainnya berisi data. Misalnya, jika ingin membuat resep, Anda dapat menggunakan perintah seperti "Anda adalah koki berbakat, buatlah resep dengan bahan-bahan berikut: ...", lalu tambahkan bahan-bahan ke akhir teks tersebut. Itulah tujuan 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 konkret, 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-1.5-flash-002")
.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 tinggi dan juga nilai topP dan topK yang tinggi. Kemudian, Anda membuat PromptTemplate
dengan metode statis from()
-nya, dengan meneruskan string perintah kita, dan menggunakan variabel placeholder kurung kurawal ganda: {{dish}}
dan {{ingredients}}
.
Anda membuat perintah akhir dengan memanggil apply()
yang menggunakan peta key-value pair yang mewakili nama placeholder dan nilai string yang akan menggantikannya.
Terakhir, Anda memanggil metode generate()
dari model Gemini dengan membuat pesan pengguna dari perintah tersebut, dengan petunjuk prompt.toUserMessage()
.
Jalankan contoh:
./gradlew run -q -DjavaMainClass=gemini.workshop.TemplatePrompt
Anda akan melihat output yang dihasilkan yang terlihat mirip dengan 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
di peta serta menyesuaikan suhu, topK
, dan tokP
, lalu jalankan kembali kode. Hal ini akan memungkinkan Anda 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 nilai yang berbeda, yang diberikan oleh pengguna.
9. Klasifikasi teks dengan perintah few-shot
LLM cukup baik dalam mengklasifikasikan teks ke dalam berbagai kategori. Anda dapat membantu LLM dalam tugas tersebut dengan memberikan beberapa contoh teks dan kategori terkait. Pendekatan ini sering disebut few-shot prompting.
Mari kita 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-1.5-flash-002")
.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
. Dan 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 tampilan definisi skema.
Setelah model dikonfigurasi, Anda membuat antarmuka SentimentAnalysis
yang akan diterapkan oleh AiServices
LangChain4j untuk Anda menggunakan LLM. Antarmuka ini berisi satu metode: analyze()
. Fungsi ini mengambil teks untuk dianalisis dalam input, dan menampilkan nilai enum Sentiment
. Jadi, Anda hanya memanipulasi objek dengan jenis yang kuat yang mewakili class sentimen yang dikenali.
Kemudian, untuk memberikan "beberapa contoh singkat" guna mendorong model untuk melakukan tugas klasifikasinya, Anda membuat memori chat untuk meneruskan pasangan pesan pengguna dan respons AI yang mewakili 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 teks dalam jumlah besar. Namun, pengetahuannya hanya mencakup informasi yang telah dilihat selama pelatihannya. Jika ada informasi baru yang dirilis setelah tanggal batas pelatihan model, detail tersebut tidak akan tersedia untuk model. Dengan demikian, 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 waktu pelatihan.
Mari kita 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 mendasarkan jawabannya, bukan hanya mengandalkan pengetahuan yang terkandung dalam pelatihannya.
Dalam RAG, ada dua fase:
- Fase penyerapan — Dokumen dimuat dalam memori, dibagi menjadi beberapa bagian yang lebih kecil, dan penyematan vektor (representasi vektor multidimensi tinggi dari bagian) 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
Untuk contoh baru ini, Anda akan mengajukan pertanyaan tentang model mobil fiktif dari produsen mobil yang juga fiktif: mobil Cymbal Starlight. Idenya adalah 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.
Mengimplementasikan chatbot
Mari kita pelajari cara membuat 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 diterapkan di class yang sama. Biasanya, Anda akan memiliki satu aplikasi yang menangani penyerapan, dan aplikasi lain yang menawarkan antarmuka chatbot kepada pengguna.
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 fase penyerapan dokumen adalah menemukan file PDF tentang mobil fiktif kita, 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 penyematan. Ini adalah model tertentu yang berperan untuk membuat representasi vektor dari potongan teks (kata, kalimat, atau bahkan paragraf). Fungsi 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 class untuk berkolaborasi bersama guna:
- Memuat dan memisahkan dokumen PDF dalam beberapa bagian.
- Buat embedding vektor untuk semua bagian 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
. Fungsi ini akan membagi teks file PDF menjadi cuplikan 500 karakter, dengan tumpang tindih 100 karakter (dengan bagian berikut, untuk menghindari pemotongan kata atau kalimat, secara terpisah).
Pengambil penyimpanan menautkan pemisah dokumen, model penyematan untuk menghitung vektor, dan database vektor dalam memori. Kemudian, metode ingest()
akan menangani proses transfer.
Sekarang, fase pertama telah selesai, dokumen telah diubah menjadi potongan teks dengan penyematan 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-1.5-flash-002")
.maxOutputTokens(1000)
.build();
Anda juga memerlukan class retriever untuk menautkan database vektor (dalam variabel embeddingStore
) dengan model penyematan. Tugasnya adalah membuat kueri database vektor dengan menghitung penyematan vektor untuk kueri pengguna, guna menemukan vektor yang serupa dalam database:
EmbeddingStoreContentRetriever retriever =
new EmbeddingStoreContentRetriever(embeddingStore, embeddingModel);
Buat antarmuka yang mewakili asisten pakar mobil, yaitu antarmuka yang akan diimplementasikan class AiServices
agar Anda dapat berinteraksi dengan model:
interface CarExpert {
Result<String> ask(String question);
}
Antarmuka CarExpert
menampilkan respons string yang digabungkan dalam class Result
LangChain4j. Mengapa menggunakan wrapper ini? Karena tidak hanya akan memberi Anda 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 mendasarkan 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 konfigurasikan sebelumnya.
- Kenangan chat untuk melacak percakapan.
- retriever 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}}
the following information:
{{contents}}
"""))
.build())
.contentRetriever(retriever)
.build())
Anda akhirnya siap untuk 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 saat 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 kutipan saham atau pengukuran sensor IoT.
- Mengirim email.
Alat komputasi:
- Kalkulator untuk soal matematika yang lebih lanjut.
- Penafsiran kode untuk menjalankan kode saat LLM memerlukan logika penalaran.
- Mengonversi permintaan bahasa alami menjadi kueri SQL sehingga LLM dapat membuat kueri database.
Panggilan fungsi (terkadang disebut alat, atau penggunaan alat) adalah kemampuan model untuk meminta satu atau beberapa panggilan fungsi yang akan dilakukan atas namanya, sehingga model dapat menjawab perintah pengguna dengan benar menggunakan 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 LLM dengan respons, dan LLM kemudian menafsirkan kembali dengan membalas dengan jawaban tekstual.
Empat langkah panggilan fungsi
Mari kita lihat contoh panggilan fungsi: mendapatkan informasi tentang perkiraan cuaca.
Jika Anda bertanya kepada Gemini atau LLM lainnya tentang cuaca di Paris, mereka akan membalas 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 menanyakan cuaca di Paris. Aplikasi chatbot (menggunakan LangChain4j) mengetahui bahwa ada satu atau beberapa fungsi yang dapat digunakan untuk membantu LLM memenuhi kueri. Chatbot mengirimkan perintah awal, serta daftar fungsi yang dapat dipanggil. Di sini, fungsi bernama getWeather()
yang menggunakan parameter string untuk lokasi.
Karena LLM tidak mengetahui prakiraan cuaca, alih-alih membalas melalui teks, LLM akan mengirimkan kembali permintaan eksekusi fungsi. Chatbot harus memanggil fungsi getWeather()
dengan "Paris"
sebagai parameter lokasi.
2️⃣ Chatbot memanggil fungsi tersebut atas nama LLM, mengambil respons fungsi. Di sini, kita bayangkan 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 cerah di Paris.
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-1.5-flash-002")
.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 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 digunakan model, dan model 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 tahu 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);
Dan 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());
Outputnya adalah:
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 di atas output, serta jawabannya.
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.
12. LangChain4j menangani panggilan fungsi
Pada langkah sebelumnya, Anda telah melihat bagaimana interaksi pertanyaan/jawaban teks normal dan permintaan/respons fungsi saling tumpang-tindih, dan di antaranya, Anda memberikan respons fungsi yang diminta secara langsung, tanpa memanggil fungsi yang sebenarnya.
Namun, LangChain4j juga menawarkan abstraksi tingkat 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 kumpulan data yang akan mewakili struktur data respons fungsi:
record WeatherForecast(String location, String forecast, int temperature) {}
Respons berisi informasi tentang lokasi, perkiraan, 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 model untuk dipanggil.
Parameter fungsi (satu di sini) juga dianotasi, tetapi dengan anotasi @P
singkat ini, yang juga memberikan deskripsi parameter. Anda dapat menambahkan fungsi sebanyak yang diinginkan, agar tersedia untuk model, untuk skenario yang lebih kompleks.
Di 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 tersebut.
Seperti yang kita lihat saat Anda membuat ToolSpecification
dalam pendekatan sebelumnya, penting untuk mendokumentasikan fungsi yang dilakukan, dan menjelaskan parameter yang sesuai. Hal ini membantu model memahami cara dan waktu fungsi ini dapat digunakan.
Selanjutnya, LangChain4j memungkinkan Anda menyediakan antarmuka yang sesuai dengan kontrak yang ingin Anda gunakan untuk berinteraksi dengan model. Di sini, ini adalah antarmuka sederhana yang mengambil string yang mewakili 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 canggih, karena objek yang lebih rumit tersebut juga berisi informasi tambahan seperti jumlah token yang digunakan, dll. Namun, untuk memudahkan, kita hanya akan menggunakan string dalam input, dan string dalam output.
Mari kita selesaikan dengan metode main()
yang menyatukan semua bagian:
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-1.5-pro-002")
.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?"));
}
Seperti biasa, Anda mengonfigurasi model chat Gemini. Kemudian, Anda membuat instance layanan prakiraan cuaca yang berisi "fungsi" yang akan diminta model untuk kita panggil.
Sekarang, Anda menggunakan class AiServices
lagi untuk mengikat model chat, memori chat, dan alat (yaitu layanan prakiraan cuaca dengan fungsinya). AiServices
menampilkan objek yang mengimplementasikan antarmuka WeatherAssistant
yang Anda tentukan. Satu-satunya hal yang tersisa adalah memanggil metode chat()
dari asisten tersebut. Saat memanggilnya, Anda hanya akan melihat respons teks, tetapi permintaan panggilan fungsi dan respons panggilan fungsi tidak akan terlihat dari developer, dan permintaan tersebut akan ditangani secara otomatis dan transparan. Jika Gemini menganggap fungsi harus dipanggil, Gemini akan membalas dengan permintaan panggilan fungsi, dan LangChain4j akan menangani pemanggilan fungsi lokal untuk 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.
Ini adalah contoh satu fungsi.
Beberapa panggilan fungsi
Anda juga dapat memiliki beberapa fungsi dan membiarkan LangChain4j menangani beberapa panggilan fungsi untuk Anda. Lihat MultiFunctionCallingAssistant.java
untuk contoh beberapa fungsi.
Fungsi 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 serta 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-1.5-flash-002")
.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 sebagai berikut:
./gradlew run -q -DjavaMainClass=gemini.workshop.MultiFunctionCallingAssistant
Dan 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.
Kepada Agen
Panggilan fungsi adalah mekanisme ekstensi yang bagus untuk model bahasa besar seperti Gemini. Hal ini memungkinkan kita membuat sistem yang lebih kompleks yang sering disebut "agen" atau "asisten AI". Agen ini dapat berinteraksi dengan dunia eksternal melalui API eksternal dan dengan layanan yang dapat memiliki efek samping pada lingkungan eksternal (seperti mengirim email, membuat tiket, dll.)
Saat membuat agen yang canggih, Anda harus melakukannya dengan bertanggung jawab. Anda harus mempertimbangkan human-in-the-loop sebelum melakukan tindakan otomatis. Penting untuk mempertimbangkan keamanan saat mendesain agen yang didukung LLM yang berinteraksi dengan dunia eksternal.
13. Menjalankan Gemma dengan Ollama dan TestContainers
Sejauh ini, kita telah menggunakan Gemini, tetapi ada juga Gemma, model adiknya.
Gemma adalah sekumpulan model terbuka yang ringan dan canggih, dibangun dari riset dan teknologi yang digunakan untuk membuat model Gemini. Gemma tersedia dalam dua variasi, Gemma1 dan Gemma2, masing-masing dengan berbagai ukuran. Gemma1 tersedia dalam dua ukuran: 2B dan 7B. Gemma2 tersedia dalam dua ukuran: 9B dan 27B. Beratnya tersedia secara gratis, 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 mengklik tombol, 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 2, Mistral, dan banyak lagi di komputer lokal. 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 mengambil model:
ollama pull gemma:2b
Tunggu hingga model ditarik. Proses ini dapat memerlukan waktu agak lama.
Jalankan model:
ollama run gemma:2b
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 pada TestContainers
Daripada harus menginstal dan menjalankan Ollama secara lokal, Anda dapat menggunakan Ollama dalam penampung, yang ditangani oleh TestContainers.
TestContainers tidak hanya berguna untuk pengujian, tetapi Anda juga dapat menggunakannya untuk menjalankan penampung. Bahkan ada OllamaContainer
tertentu yang dapat Anda manfaatkan.
Berikut adalah gambaran lengkapnya:
Penerapan
Mari kita lihat GemmaWithOllamaContainer.java
, bagian demi bagian.
Pertama, Anda perlu membuat penampung Ollama turunan yang menarik model Gemma. Image ini sudah ada dari operasi 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_GEMMA_2_B = "tc-ollama-gemma-2b";
// Creating an Ollama container with Gemma 2B 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_GEMMA_2_B)
.exec();
if (listImagesCmd.isEmpty()) {
System.out.println("Creating a new Ollama container with Gemma 2B image...");
OllamaContainer ollama = new OllamaContainer("ollama/ollama:0.1.26");
ollama.start();
ollama.execInContainer("ollama", "pull", "gemma:2b");
ollama.commitToImage(TC_OLLAMA_GEMMA_2_B);
return ollama;
} else {
System.out.println("Using existing Ollama container with Gemma 2B image...");
// Substitute the default Ollama image with our Gemma variant
return new OllamaContainer(
DockerImageName.parse(TC_OLLAMA_GEMMA_2_B)
.asCompatibleSubstituteFor("ollama/ollama"));
}
}
Selanjutnya, Anda membuat dan memulai penampung pengujian Ollama, lalu membuat model chat Ollama, dengan mengarahkan alamat dan port penampung ke model yang ingin Anda gunakan. Terakhir, Anda hanya 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:2b")
.build();
String response = model.generate("Why is the sky blue?");
System.out.println(response);
}
Jalankan sebagai berikut:
./gradlew run -q -DjavaMainClass=gemini.workshop.GemmaWithOllamaContainer
Pengoperasian pertama akan memerlukan waktu beberapa saat untuk membuat dan menjalankan penampung, tetapi setelah selesai, Anda akan melihat Gemma merespons:
INFO: Container ollama/ollama:0.1.26 started in PT2.827064047S
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 mem-build aplikasi chat AI Generatif pertama Anda di Java menggunakan LangChain4j dan Gemini API. Anda telah menemukan bahwa model bahasa besar multimodal cukup canggih dan mampu menangani berbagai tugas seperti pertanyaan/jawaban, bahkan pada dokumentasi Anda sendiri, ekstraksi data, berinteraksi dengan API eksternal, dan lainnya.
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