1. מבוא
ה-Codelab הזה מתמקד במודל השפה הגדול (LLM) Gemini, שמארח ב-Vertex AI ב-Google Cloud. Vertex AI היא פלטפורמה שכוללת את כל המוצרים, השירותים והמודלים של למידת מכונה ב-Google Cloud.
תשתמשו ב-Java כדי ליצור אינטראקציה עם Gemini API באמצעות ה-framework של LangChain4j. תעברו על דוגמאות קונקרטיות כדי ללמוד איך להשתמש ב-LLM למענה על שאלות, ליצירת רעיונות, לחילוץ ישויות ותוכן מובנה, ליצירה משולבת-אחזור (RAG) ולבקשות להפעלת פונקציות.
מה זה AI גנרטיבי?
בינה מלאכותית גנרטיבית היא שימוש בבינה מלאכותית כדי ליצור תוכן חדש, כמו טקסט, תמונות, מוזיקה, אודיו וסרטונים.
בינה מלאכותית גנרטיבית מבוססת על מודלים גדולים של שפה (LLM) שיכולים לבצע כמה משימות בו-זמנית, וגם משימות מוכנות לשימוש כמו סיכום, שאלות ותשובות, סיווג ועוד. עם אימון מינימלי, אפשר להתאים מודלים בסיסיים לתרחישי שימוש ממוקדים עם מעט מאוד נתונים לדוגמה.
איך פועלת בינה מלאכותית גנרטיבית?
בינה מלאכותית גנרטיבית פועלת באמצעות מודל של למידת מכונה (ML) כדי ללמוד את הדפוסים והקשרים במערך נתונים של תוכן שנוצר על ידי בני אדם. לאחר מכן היא משתמשת בדפוסים שנלמדו כדי ליצור תוכן חדש.
הדרך הכי נפוצה לאמן מודל AI גנרטיבי היא באמצעות למידה מפוקחת. נותנים למודל קבוצה של תוכן שנוצר על ידי בני אדם ותוויות תואמות. לאחר מכן, היא לומדת ליצור תוכן שדומה לתוכן שנוצר על ידי בני אדם.
מהם יישומים נפוצים של AI גנרטיבי?
אפשר להשתמש ב-AI גנרטיבי כדי:
- שיפור האינטראקציות עם הלקוחות באמצעות חוויות משופרות בצ'אט ובחיפוש.
- אפשר לעיין בכמויות גדולות של נתונים לא מובנים באמצעות ממשקים שיחתיים וסיכומים.
- עזרה במשימות חוזרות כמו מענה לבקשות להצעות מחיר, התאמת תוכן שיווקי לשוק המקומי בשפות שונות ובדיקת חוזים של לקוחות לצורך עמידה בדרישות, ועוד.
אילו מוצרים מבוססי-AI גנרטיבי זמינים ב-Google Cloud?
באמצעות Vertex AI, אתם יכולים ליצור אינטראקציה עם מודלים בסיסיים, להתאים אותם אישית ולהטמיע אותם באפליקציות שלכם, גם אם אין לכם ידע רב בלמידת מכונה. אתם יכולים לגשת למודלים בסיסיים ב-Model Garden, לכוונן מודלים באמצעות ממשק משתמש פשוט ב-Vertex AI Studio או להשתמש במודלים במחברת של מדעי הנתונים.
חיפוש ושיחות על בסיס Vertex AI מציע למפתחים את הדרך המהירה ביותר ליצור מנועי חיפוש וצ'אטבוטים שמבוססים על AI גנרטיבי.
מופעל על ידי Gemini, Gemini ל-Google Cloud הוא כלי מבוסס-AI שזמין ב-Google Cloud ובסביבות פיתוח משולבות (IDE) כדי לעזור לכם לבצע יותר משימות מהר יותר. Gemini Code Assist מספק השלמת קוד, יצירת קוד והסברים על קוד, ומאפשר לכם לשוחח איתו כדי לשאול שאלות טכניות.
מה זה Gemini?
Gemini היא משפחה של מודלים של AI גנרטיבי שפותחו על ידי Google DeepMind ומיועדים לתרחישי שימוש מולטי-מודאליים. מולטי-מודאליות פירושה שהמודל יכול לעבד וליצור סוגים שונים של תוכן, כמו טקסט, קוד, תמונות ואודיו.

Gemini זמין בווריאציות ובגדלים שונים:
- Gemini 2.0 Flash: התכונות החדשות ביותר מהדור הבא והיכולות המשופרות שלנו.
- Gemini 2.0 Flash-Lite: מודל Gemini 2.0 Flash שעבר אופטימיזציה ליעילות בעלויות ולזמן אחזור נמוך.
- Gemini 2.5 Pro: המודל הכי מתקדם שלנו לחשיבה רציונלית משופרת.
- Gemini 2.5 Flash: מודל חושב שמציע יכולות מגוונות. היא נועדה להציע איזון בין מחיר לביצועים.
התכונות העיקריות:
- מרובה מצבים: היכולת של Gemini להבין ולטפל בפורמטים שונים של מידע היא צעד משמעותי מעבר למודלים מסורתיים של שפה שמבוססים על טקסט בלבד.
- ביצועים: הביצועים של Gemini 2.5 Pro טובים יותר מהביצועים של המודלים המתקדמים ביותר כיום במדדים רבים, והוא היה המודל הראשון שהשיג תוצאות טובות יותר ממומחים אנושיים במדד המאתגר MMLU (הבנת שפה מרובת משימות).
- גמישות: הגדלים השונים של Gemini מאפשרים להתאים אותו לתרחישי שימוש שונים, ממחקר בקנה מידה גדול ועד לפריסה במכשירים ניידים.
איך אפשר לקיים אינטראקציה עם Gemini ב-Vertex AI מ-Java?
יש לך שתי אפשרויות:
- הספרייה הרשמית של Vertex AI Java API for Gemini.
- LangChain4j framework.
בשיעור Codelab הזה נשתמש ב-framework LangChain4j.
מהו ה-framework LangChain4j?
המסגרת LangChain4j היא ספרייה בקוד פתוח לשילוב של מודלים גדולים של שפה (LLM) באפליקציות Java. היא עושה זאת באמצעות תזמור של רכיבים שונים, כמו ה-LLM עצמו, אבל גם כלים אחרים כמו מסדי נתונים וקטוריים (לחיפושים סמנטיים), טוענים ומפצלים של מסמכים (לניתוח מסמכים ולמידה מהם), מנתחי פלט ועוד.
הפרויקט הזה קיבל השראה מפרויקט Python של LangChain, אבל המטרה שלו היא לשרת מפתחי Java.

מה תלמדו
- איך מגדירים פרויקט Java לשימוש ב-Gemini וב-LangChain4j
- איך שולחים את הפרומפט הראשון ל-Gemini באופן פרוגרמטי
- איך משדרים תשובות מ-Gemini
- איך יוצרים שיחה בין משתמש ל-Gemini
- איך משתמשים ב-Gemini בהקשר מולטי-מודאלי על ידי שליחת טקסט ותמונות
- איך לחלץ מידע מובנה שימושי מתוכן לא מובנה
- איך משנים תבניות של הנחיות
- איך לבצע סיווג טקסטים כמו ניתוח סנטימנטים
- איך מתכתבים בצ'אט עם מסמכים משלכם (Retrieval Augmented Generation)
- איך מרחיבים את הצ'אטבוטים באמצעות בקשה להפעלת פונקציה
- איך משתמשים ב-Ollama וב-TestContainers כדי להריץ באופן מקומי את Gemma
מה תצטרכו
- ידע בשפת התכנות Java
- פרויקט ב-Google Cloud
- דפדפן, כמו Chrome או Firefox
2. הגדרה ודרישות
הגדרת סביבה בקצב אישי
- נכנסים ל-מסוף Google Cloud ויוצרים פרויקט חדש או משתמשים בפרויקט קיים. אם עדיין אין לכם חשבון Gmail או Google Workspace, אתם צריכים ליצור חשבון.



- שם הפרויקט הוא השם המוצג למשתתפים בפרויקט. זו מחרוזת תווים שלא נמצאת בשימוש ב-Google APIs. תמיד אפשר לעדכן את המיקום.
- מזהה הפרויקט הוא ייחודי לכל הפרויקטים ב-Google Cloud ואי אפשר לשנות אותו אחרי שהוא מוגדר. מסוף Cloud יוצר באופן אוטומטי מחרוזת ייחודית, ובדרך כלל לא צריך לדעת מה היא. ברוב ה-Codelabs, תצטרכו להפנות למזהה הפרויקט (בדרך כלל מסומן כ-
PROJECT_ID). אם אתם לא אוהבים את המזהה שנוצר, אתם יכולים ליצור מזהה אקראי אחר. אפשר גם לנסות שם משתמש משלכם ולבדוק אם הוא זמין. אי אפשר לשנות את ההגדרה הזו אחרי השלב הזה, והיא נשארת לאורך הפרויקט. - לידיעתכם, יש ערך שלישי, מספר פרויקט, שחלק מממשקי ה-API משתמשים בו. מידע נוסף על שלושת הערכים האלה מופיע במאמרי העזרה.
- בשלב הבא, תצטרכו להפעיל את החיוב במסוף Cloud כדי להשתמש במשאבי Cloud או בממשקי API של Cloud. השלמת ה-codelab הזה לא תעלה לכם הרבה, אם בכלל. כדי להשבית את המשאבים ולמנוע חיובים נוספים אחרי שתסיימו את המדריך הזה, תוכלו למחוק את המשאבים שיצרתם או למחוק את הפרויקט. משתמשים חדשים ב-Google Cloud זכאים לתוכנית תקופת ניסיון בחינם בשווי 300$.
מפעילים את Cloud Shell
אפשר להפעיל את Google Cloud מרחוק מהמחשב הנייד, אבל ב-Codelab הזה נשתמש ב-Cloud Shell, סביבת שורת פקודה שפועלת בענן.
הפעלת Cloud Shell
- ב-Cloud Console, לוחצים על Activate Cloud Shell
.

אם זו הפעם הראשונה שאתם מפעילים את Cloud Shell, יוצג לכם מסך ביניים עם תיאור של השירות. אם מוצג לכם מסך ביניים, לוחצים על המשך.

יחלפו כמה רגעים עד שההקצאה והחיבור ל-Cloud Shell יושלמו.

המכונה הווירטואלית הזו כוללת את כל הכלים הדרושים למפתחים. יש בה ספריית בית בנפח מתמיד של 5GB והיא פועלת ב-Google Cloud, מה שמשפר מאוד את הביצועים והאימות של הרשת. אפשר לבצע את רוב העבודה ב-codelab הזה, אם לא את כולה, באמצעות דפדפן.
אחרי שמתחברים ל-Cloud Shell, אמור להופיע אימות ושהפרויקט מוגדר לפי מזהה הפרויקט.
- מריצים את הפקודה הבאה ב-Cloud Shell כדי לוודא שאתם מאומתים:
gcloud auth list
פלט הפקודה
Credentialed Accounts
ACTIVE ACCOUNT
* <my_account>@<my_domain.com>
To set the active account, run:
$ gcloud config set account `ACCOUNT`
- מריצים את הפקודה הבאה ב-Cloud Shell כדי לוודא שפקודת gcloud מכירה את הפרויקט:
gcloud config list project
פלט הפקודה
[core] project = <PROJECT_ID>
אם לא, אפשר להגדיר אותו באמצעות הפקודה הזו:
gcloud config set project <PROJECT_ID>
פלט הפקודה
Updated property [core/project].
3. הכנת סביבת הפיתוח
בשיעור Codelab הזה תשתמשו בטרמינל Cloud Shell וב-Cloud Shell Editor כדי לפתח את תוכנות Java.
הפעלת ממשקי Vertex AI API
במסוף Google Cloud, מוודאים ששם הפרויקט מוצג בראש מסוף Google Cloud. אם לא, לוחצים על בחירת פרויקט כדי לפתוח את בורר הפרויקטים, ובוחרים את הפרויקט הרצוי.
אפשר להפעיל את ממשקי ה-API של Vertex AI דרך הקטע Vertex AI במסוף Google Cloud או דרך מסוף Cloud Shell.
כדי להפעיל את התכונה דרך מסוף Google Cloud, קודם צריך לעבור לקטע Vertex AI בתפריט של מסוף Google Cloud:

לוחצים על Enable All Recommended APIs (הפעלת כל ממשקי ה-API המומלצים) במרכז הבקרה של Vertex AI.
כך יופעלו כמה ממשקי API, אבל החשוב ביותר ל-codelab הוא aiplatform.googleapis.com.
אפשר גם להפעיל את ה-API הזה מהטרמינל של Cloud Shell באמצעות הפקודה הבאה:
gcloud services enable aiplatform.googleapis.com
משכפלים את מאגר GitHub
בטרמינל של Cloud Shell, משכפלים את המאגר של ה-Codelab הזה:
git clone https://github.com/glaforge/gemini-workshop-for-java-developers.git
כדי לוודא שהפרויקט מוכן להרצה, אפשר לנסות להריץ את התוכנית Hello World.
מוודאים שאתם נמצאים בתיקייה ברמה העליונה:
cd gemini-workshop-for-java-developers/
יוצרים את Gradle wrapper:
gradle wrapper
הפעלה באמצעות gradlew:
./gradlew run
הפלט הבא אמור להתקבל:
.. > Task :app:run Hello World!
פתיחה והגדרה של Cloud Editor
פותחים את הקוד באמצעות Cloud Code Editor מ-Cloud Shell:

ב-Cloud Code Editor, פותחים את תיקיית המקור של ה-Codelab על ידי בחירה באפשרות File -> Open Folder ומצביעים על תיקיית המקור של ה-Codelab (למשל, /home/username/gemini-workshop-for-java-developers/).
הגדרת משתני סביבה
פותחים טרמינל חדש ב-Cloud Code Editor על ידי בחירה באפשרות Terminal -> New Terminal. מגדירים שני משתני סביבה שנדרשים להרצת דוגמאות הקוד:
- PROJECT_ID – מזהה הפרויקט ב-Google Cloud
- LOCATION – האזור שבו מודל Gemini נפרס
מייצאים את המשתנים באופן הבא:
export PROJECT_ID=$(gcloud config get-value project) export LOCATION=us-central1
4. הקריאה הראשונה למודל Gemini
עכשיו, אחרי שהפרויקט מוגדר בצורה תקינה, הגיע הזמן לקרוא ל-Gemini API.
רוצה לראות את QA.java במאגר app/src/main/java/gemini/workshop?
package gemini.workshop;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.model.chat.ChatLanguageModel;
public class QA {
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.build();
System.out.println(model.generate("Why is the sky blue?"));
}
}
בדוגמה הראשונה הזו, צריך לייבא את המחלקה VertexAiGeminiChatModel, שמטמיעה את הממשק ChatModel.
בשיטה main, מגדירים את מודל השפה של הצ'אט באמצעות ה-builder של VertexAiGeminiChatModel ומציינים:
- פרויקט
- מיקום
- שם המודל (
gemini-2.0-flash).
עכשיו מודל השפה מוכן, ואפשר לקרוא לשיטה generate() ולהעביר את הפרומפט, השאלה או ההוראות כדי לשלוח אותם ל-LLM. כאן, שואלים שאלה פשוטה על הסיבה לכך שהשמיים כחולים.
אתם מוזמנים לשנות את ההנחיה הזו כדי לנסות שאלות או משימות אחרות.
מריצים את הדוגמה בתיקיית הבסיס של קוד המקור:
./gradlew run -q -DjavaMainClass=gemini.workshop.QA
הפלט אמור להיראות כך:
The sky appears blue because of a phenomenon called Rayleigh scattering. When sunlight enters the atmosphere, it is made up of a mixture of different wavelengths of light, each with a different color. The different wavelengths of light interact with the molecules and particles in the atmosphere in different ways. The shorter wavelengths of light, such as those corresponding to blue and violet light, are more likely to be scattered in all directions by these particles than the longer wavelengths of light, such as those corresponding to red and orange light. This is because the shorter wavelengths of light have a smaller wavelength and are able to bend around the particles more easily. As a result of Rayleigh scattering, the blue light from the sun is scattered in all directions, and it is this scattered blue light that we see when we look up at the sky. The blue light from the sun is not actually scattered in a single direction, so the color of the sky can vary depending on the position of the sun in the sky and the amount of dust and water droplets in the atmosphere.
כל הכבוד, ביצעת את השיחה הראשונה שלך עם Gemini!
הצגת התשובה באופן שוטף
האם שמת לב שהתשובה ניתנה בבת אחת, אחרי כמה שניות? אפשר גם לקבל את התשובה באופן הדרגתי, בזכות וריאנט התשובה בסטרימינג. התשובה מוזרמת, המודל מחזיר את התשובה חלק אחר חלק, כשהיא זמינה.
ב-codelab הזה נשתמש בתשובה ללא סטרימינג, אבל נבדוק את התשובה עם סטרימינג כדי לראות איך אפשר לעשות את זה.
ב- StreamQA.java בספרייה app/src/main/java/gemini/workshop אפשר לראות את התשובה בסטרימינג בפעולה:
package gemini.workshop;
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiStreamingChatModel;
import static dev.langchain4j.model.LambdaStreamingResponseHandler.onNext;
public class StreamQA {
public static void main(String[] args) {
StreamingChatLanguageModel model = VertexAiGeminiStreamingChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.maxOutputTokens(4000)
.build();
model.generate("Why is the sky blue?", onNext(System.out::println));
}
}
הפעם, אנחנו מייבאים את הווריאציות של המחלקה לסטרימינג VertexAiGeminiStreamingChatModel שמטמיעה את הממשק StreamingChatLanguageModel. תצטרכו גם לייבא סטטית את LambdaStreamingResponseHandler.onNext, שהיא שיטה נוחה שמספקת StreamingResponseHandler כדי ליצור handler לסטרימינג באמצעות ביטויי למדה של Java.
הפעם, החתימה של השיטה generate() קצת שונה. במקום להחזיר מחרוזת, סוג ההחזרה הוא void. בנוסף להנחיה, צריך להעביר handler של תגובה בסטרימינג. כאן, הודות לייבוא הסטטי שציינו למעלה, אפשר להגדיר ביטוי למבדה שמועבר לשיטת onNext(). הביטוי lambda נקרא בכל פעם שחלק חדש מהתשובה זמין, בעוד שהאחרון נקרא רק אם מתרחשת שגיאה.
הרצה:
./gradlew run -q -DjavaMainClass=gemini.workshop.StreamQA
תקבלו תשובה דומה לזו שקיבלתם בכיתה הקודמת, אבל הפעם תשימו לב שהתשובה מופיעה בהדרגה במעטפת, במקום לחכות להצגת התשובה המלאה.
הגדרה נוספת
בהגדרה, הגדרנו רק את הפרויקט, המיקום ושם המודל, אבל יש עוד פרמטרים שאפשר לציין עבור המודל:
-
temperature(Float temp)– כדי להגדיר את רמת היצירתיות של התשובה (0 היא רמת יצירתיות נמוכה ולרוב יותר עובדתית, ו-2 היא רמת יצירתיות גבוהה יותר) -
topP(Float topP)– כדי לבחור את המילים האפשריות שההסתברות הכוללת שלהן היא המספר הזה (בין 0 ל-1) -
topK(Integer topK)– כדי לבחור מילה באופן אקראי מתוך מספר מקסימלי של מילים סבירות להשלמת הטקסט (מ-1 עד 40) -
maxOutputTokens(Integer max)– כדי לציין את האורך המקסימלי של התשובה שהמודל נותן (באופן כללי, 4 טוקנים מייצגים בערך 3 מילים) -
maxRetries(Integer retries)— אם חרגתם מהמכסה של בקשות לכל פרק זמן, או אם יש בעיה טכנית בפלטפורמה, תוכלו להגדיר שהמודל ינסה לבצע את הקריאה 3 פעמים
עד עכשיו שאלתם את Gemini שאלה אחת, אבל אתם יכולים גם לנהל איתו שיחה רב-שלבית. בקטע הבא נסביר איך עושים את זה.
5. שיחה עם Gemini
בשלב הקודם שאלתם שאלה אחת. עכשיו הגיע הזמן לנהל שיחה אמיתית בין משתמש לבין מודל LLM. כל שאלה ותשובה יכולות להסתמך על הקודמות כדי ליצור דיון אמיתי.
כדאי לעיין ב-Conversation.java בתיקייה app/src/main/java/gemini/workshop:
package gemini.workshop;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.service.AiServices;
import java.util.List;
public class Conversation {
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.build();
MessageWindowChatMemory chatMemory = MessageWindowChatMemory.builder()
.maxMessages(20)
.build();
interface ConversationService {
String chat(String message);
}
ConversationService conversation =
AiServices.builder(ConversationService.class)
.chatLanguageModel(model)
.chatMemory(chatMemory)
.build();
List.of(
"Hello!",
"What is the country where the Eiffel tower is situated?",
"How many inhabitants are there in that country?"
).forEach( message -> {
System.out.println("\nUser: " + message);
System.out.println("Gemini: " + conversation.chat(message));
});
}
}
כמה ייבואים חדשים ומעניינים בכיתה הזו:
-
MessageWindowChatMemory— class שיעזור לטפל בהיבט של שיחה רב-שלבית, ולשמור בזיכרון המקומי את השאלות והתשובות הקודמות -
AiServices— מחלקת הפשטה ברמה גבוהה יותר שתקשר בין מודל הצ'אט לבין זיכרון הצ'אט
בשיטה הראשית, תגדירו את המודל, את זיכרון הצ'אט ואת שירות ה-AI. המודל מוגדר כרגיל עם פרטי הפרויקט, המיקום ושם המודל.
בזיכרון של הצ'אט, אנחנו משתמשים בבונה של MessageWindowChatMemory כדי ליצור זיכרון שכולל את 20 ההודעות האחרונות שהוחלפו. זהו חלון הזזה מעל השיחה, שההקשר שלה נשמר באופן מקומי בלקוח של מחלקת Java שלנו.
לאחר מכן יוצרים את AI service שמקשר בין מודל הצ'אט לזיכרון הצ'אט.
שימו לב איך שירות ה-AI משתמש בממשק ConversationService מותאם אישית שהגדרנו, שמיושם על ידי LangChain4j, ומקבל String שאילתה ומחזיר String תשובה.
עכשיו הגיע הזמן לנהל שיחה עם Gemini. קודם נשלחת ברכה פשוטה, ואז שאלה ראשונה על מגדל אייפל כדי לדעת באיזו מדינה הוא נמצא. שימו לב שהמשפט האחרון קשור לתשובה של השאלה הראשונה, כי אתם רוצים לדעת כמה תושבים יש במדינה שבה ממוקם מגדל אייפל, בלי לציין במפורש את המדינה שצוינה בתשובה הקודמת. הוא מראה ששאלות ותשובות קודמות נשלחות עם כל הנחיה.
מריצים את הדוגמה:
./gradlew run -q -DjavaMainClass=gemini.workshop.Conversation
אמורות להופיע שלוש תשובות שדומות לתשובות הבאות:
User: Hello! Gemini: Hi there! How can I assist you today? User: What is the country where the Eiffel tower is situated? Gemini: France User: How many inhabitants are there in that country? Gemini: As of 2023, the population of France is estimated to be around 67.8 million.
אתם יכולים לשאול את Gemini שאלות חד-שלביות או לנהל איתו שיחות רב-שלביות, אבל עד עכשיו, הקלט היה רק טקסט. מה לגבי תמונות? בשלב הבא נבדוק תמונות.
6. מולטי-מודאליות עם Gemini
Gemini הוא מודל מולטי-מודאלי. הוא לא רק מקבל קלט של טקסט, אלא גם של תמונות או אפילו סרטונים. בקטע הזה מוצג תרחיש לדוגמה של שילוב טקסט ותמונות.
האם לדעתך Gemini יזהה את החתול הזה?

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

- שלב השאילתה – המשתמשים יכולים עכשיו לשאול שאלות לגבי המסמכים. גם השאלה תומר לווקטור ותושווה לכל הווקטורים האחרים במסד הנתונים. הווקטורים הדומים ביותר קשורים בדרך כלל מבחינה סמנטית ומוחזרים על ידי מסד הנתונים הווקטורי. לאחר מכן, ה-LLM מקבל את ההקשר של השיחה, את חלקי הטקסט שתואמים לווקטורים שהוחזרו ממסד הנתונים, ומבקשים ממנו לבסס את התשובה על חלקי הטקסט האלה.

הכנת המסמכים
בדוגמה החדשה הזו, תשאל שאלות על דגם רכב פיקטיבי של יצרן רכב פיקטיבי: מכונית Cymbal Starlight! הרעיון הוא שמסמך על מכונית פיקטיבית לא צריך להיות חלק מהידע של המודל. לכן, אם Gemini יוכל לענות על שאלות לגבי המכונית הזו בצורה נכונה, זה אומר שגישת ה-RAG עובדת: הוא יכול לחפש במסמך שלכם.
הטמעה של הצ'אט בוט
בואו נראה איך לבנות את הגישה הדו-שלבית: קודם הטמעת המסמך, ואז זמן השאילתה (שנקרא גם 'שלב האחזור') כשמשתמשים שואלים שאלות לגבי המסמך.
בדוגמה הזו, שני השלבים מיושמים באותה מחלקה. בדרך כלל, יש אפליקציה אחת שמטפלת בהטמעה ואפליקציה אחרת שמציעה למשתמשים את הממשק של הצ'אטבוט.
בנוסף, בדוגמה הזו נשתמש במסד נתונים וקטורי בזיכרון. בתרחיש ייצור אמיתי, שלבי ההטמעה והשאילתה יופרדו לשתי אפליקציות נפרדות, והווקטורים יישמרו במסד נתונים עצמאי.
הטמעת מסמכים
השלב הראשון בתהליך ההטמעה של המסמך הוא לאתר את קובץ ה-PDF על המכונית הדמיונית שלנו ולהכין PdfParser כדי לקרוא אותו:
URL url = new URI("https://raw.githubusercontent.com/meteatamel/genai-beyond-basics/main/samples/grounding/vertexai-search/cymbal-starlight-2024.pdf").toURL();
ApachePdfBoxDocumentParser pdfParser = new ApachePdfBoxDocumentParser();
Document document = pdfParser.parse(url.openStream());
במקום ליצור קודם מודל שפה רגיל לשיחה, יוצרים מופע של מודל הטמעה. זהו מודל ספציפי שתפקידו ליצור ייצוגים וקטוריים של קטעי טקסט (מילים, משפטים או אפילו פסקאות). היא מחזירה וקטורים של מספרים עם נקודה עשרונית, ולא תגובות טקסטואליות.
VertexAiEmbeddingModel embeddingModel = VertexAiEmbeddingModel.builder()
.endpoint(System.getenv("LOCATION") + "-aiplatform.googleapis.com:443")
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.publisher("google")
.modelName("text-embedding-005")
.maxRetries(3)
.build();
לאחר מכן, תצטרכו כמה כיתות כדי לעבוד יחד על:
- טוענים את מסמך ה-PDF ומפצלים אותו לחלקים.
- יוצרים הטמעות וקטוריות לכל החלקים האלה.
InMemoryEmbeddingStore<TextSegment> embeddingStore =
new InMemoryEmbeddingStore<>();
EmbeddingStoreIngestor storeIngestor = EmbeddingStoreIngestor.builder()
.documentSplitter(DocumentSplitters.recursive(500, 100))
.embeddingModel(embeddingModel)
.embeddingStore(embeddingStore)
.build();
storeIngestor.ingest(document);
מוקם מופע של InMemoryEmbeddingStore, מסד נתונים וקטורי בזיכרון, כדי לאחסן את הטמעות הווקטורים.
המסמך מחולק לחלקים קטנים באמצעות המחלקה DocumentSplitters. הוא יפצל את הטקסט של קובץ ה-PDF לקטעים של 500 תווים, עם חפיפה של 100 תווים (עם החלק הבא, כדי להימנע מחיתוך מילים או משפטים, לחתיכות קטנות).
הכלי להוספת נתונים לחנות מקשר בין הכלי לפיצול המסמך, מודל ההטמעה לחישוב הווקטורים ומסד הנתונים של הווקטורים בזיכרון. לאחר מכן, הפונקציה ingest() תדאג להעברת הנתונים.
עכשיו, השלב הראשון הסתיים, המסמך עבר טרנספורמציה לחלקים של טקסט עם הטבעות וקטוריות משויכות, והוא מאוחסן במסד הנתונים הווקטורי.
שאלת שאלות
הגיע הזמן להתכונן לשאול שאלות! כדי להתחיל את השיחה, יוצרים מודל צ'אט:
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.maxOutputTokens(1000)
.build();
צריך גם מחלקה לאחזור כדי לקשר בין מסד הנתונים של הווקטורים (במשתנה embeddingStore) לבין מודל ההטמעה. התפקיד שלו הוא לשלוח שאילתה למסד הנתונים הווקטורי על ידי חישוב הטמעה וקטורית לשאילתה של המשתמש, כדי למצוא וקטורים דומים במסד הנתונים:
EmbeddingStoreContentRetriever retriever =
new EmbeddingStoreContentRetriever(embeddingStore, embeddingModel);
תצור ממשק שמייצג עוזר מומחה לרכב, כלומר ממשק שהמחלקה AiServices תטמיע כדי שתוכלו ליצור אינטראקציה עם המודל:
interface CarExpert {
Result<String> ask(String question);
}
ממשק CarExpert מחזיר תגובה של מחרוזת שעטופה במחלקה Result של LangChain4j. למה כדאי להשתמש ב-wrapper הזה? כי הוא לא רק ייתן לכם את התשובה, אלא גם יאפשר לכם לבדוק את חלקי הנתונים ממסד הנתונים שהוחזרו על ידי הכלי לאחזור תוכן. כך תוכלו להציג למשתמש את המקורות של המסמכים ששימשו להצגת התשובה הסופית.
בשלב הזה, אפשר להגדיר שירות AI חדש:
CarExpert expert = AiServices.builder(CarExpert.class)
.chatLanguageModel(model)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.contentRetriever(retriever)
.build();
השירות הזה משלב בין:
- מודל שפת הצ'אט שהגדרתם קודם.
- זיכרון צ'אט שיעזור לכם לעקוב אחרי השיחה.
- רכיב המאחזר משווה שאילתת הטמעת וקטורים לווקטורים במסד הנתונים.
.retrievalAugmentor(DefaultRetrievalAugmentor.builder()
.contentInjector(DefaultContentInjector.builder()
.promptTemplate(PromptTemplate.from("""
You are an expert in car automotive, and you answer concisely.
Here is the question: {{userMessage}}
Answer using the following information:
{{contents}}
"""))
.build())
.contentRetriever(retriever)
.build())
עכשיו אפשר לשאול את השאלות.
List.of(
"What is the cargo capacity of Cymbal Starlight?",
"What's the emergency roadside assistance phone number?",
"Are there some special kits available on that car?"
).forEach(query -> {
Result<String> response = expert.ask(query);
System.out.printf("%n=== %s === %n%n %s %n%n", query, response.content());
System.out.println("SOURCE: " + response.sources().getFirst().textSegment().text());
});
קוד המקור המלא נמצא ב-RAG.java בספרייה app/src/main/java/gemini/workshop.
מריצים את הדוגמה:
./gradlew -q run -DjavaMainClass=gemini.workshop.RAG
בפלט, אמורות להופיע תשובות לשאלות שלכם:
=== What is the cargo capacity of Cymbal Starlight? === The Cymbal Starlight 2024 has a cargo capacity of 13.5 cubic feet. SOURCE: Cargo The Cymbal Starlight 2024 has a cargo capacity of 13.5 cubic feet. The cargo area is located in the trunk of the vehicle. To access the cargo area, open the trunk lid using the trunk release lever located in the driver's footwell. When loading cargo into the trunk, be sure to distribute the weight evenly. Do not overload the trunk, as this could affect the vehicle's handling and stability. Luggage === What's the emergency roadside assistance phone number? === The emergency roadside assistance phone number is 1-800-555-1212. SOURCE: Chapter 18: Emergencies Roadside Assistance If you experience a roadside emergency, such as a flat tire or a dead battery, you can call roadside assistance for help. Roadside assistance is available 24 hours a day, 7 days a week. To call roadside assistance, dial the following number: 1-800-555-1212 When you call roadside assistance, be prepared to provide the following information: Your name and contact information Your vehicle's make, model, and year Your vehicle's location === Are there some special kits available on that car? === Yes, the Cymbal Starlight comes with a tire repair kit. SOURCE: Lane keeping assist: This feature helps to keep you in your lane by gently steering the vehicle back into the lane if you start to drift. Adaptive cruise control: This feature automatically adjusts your speed to maintain a safe following distance from the vehicle in front of you. Forward collision warning: This feature warns you if you are approaching another vehicle too quickly. Automatic emergency braking: This feature can automatically apply the brakes to avoid a collision.
11. בקשה להפעלת פונקציה
יש מצבים שבהם רוצים שלמודל שפה גדול תהיה גישה למערכות חיצוניות, כמו API אינטרנטי מרוחק שמקבל מידע או מבצע פעולה, או שירותים שמבצעים סוג כלשהו של חישוב. לדוגמה:
ממשקי API מרוחקים באינטרנט:
- מעקב אחרי הזמנות של לקוחות ועדכון שלהן.
- מחפשים או יוצרים כרטיס במערכת למעקב אחרי בעיות.
- אחזור נתונים בזמן אמת, כמו שערי מניות או מדידות של חיישני IoT.
- לשלוח אימייל.
כלי חישוב:
- מחשבון לבעיות מתמטיות מתקדמות יותר.
- הסבר על קוד להרצת קוד כשמודלים גדולים של שפה (LLM) צריכים לוגיקה של הסקת מסקנות.
- המרת בקשות בשפה טבעית לשאילתות SQL כדי שמודל LLM יוכל לשלוח שאילתות למסד נתונים.
קריאה לפונקציה (נקראת לפעמים 'כלים' או 'שימוש בכלים') היא היכולת של המודל לבקש לבצע קריאה אחת או יותר לפונקציה בשמו, כדי שיוכל לענות בצורה נכונה להנחיה של המשתמש עם נתונים עדכניים יותר.
בהינתן פרומפט מסוים ממשתמש, וידע על פונקציות קיימות שיכולות להיות רלוונטיות להקשר הזה, מודל שפה גדול יכול להשיב עם בקשה להפעלת פונקציה. האפליקציה שמשלבת את ה-LLM יכולה לקרוא לפונקציה בשמו, ואז להשיב ל-LLM בתגובה, וה-LLM מפרש את התגובה ומשיב בתשובה טקסטואלית.
ארבעת השלבים של הפעלת פונקציות
נבחן עכשיו דוגמה לשימוש בפונקציות: קבלת מידע על תחזית מזג האוויר.
אם תשאלו את Gemini או כל מודל LLM אחר לגבי מזג האוויר בפריז, הם יענו שאין להם מידע על תחזית מזג האוויר הנוכחית. אם רוצים של-LLM תהיה גישה בזמן אמת לנתוני מזג האוויר, צריך להגדיר כמה פונקציות שהוא יכול לבקש להשתמש בהן.
התרשים הבא ממחיש את התהליך:

1️⃣ קודם, משתמש שואל על מזג האוויר בפריז. אפליקציית הצ'אטבוט (שמבוססת על LangChain4j) יודעת שיש לרשותה פונקציה אחת או יותר שיכולות לעזור למודל השפה הגדול (LLM) להשיב לשאילתה. הצ'אטבוט שולח גם את ההנחיה הראשונית וגם את רשימת הפונקציות שאפשר להפעיל. בדוגמה הזו, יש פונקציה בשם getWeather() שמקבלת פרמטר מחרוזת למיקום.

מכיוון שמודל ה-LLM לא יודע מהן תחזיות מזג האוויר, במקום להשיב באמצעות טקסט, הוא שולח בקשה להפעלת פונקציה. הצ'אטבוט צריך להפעיל את הפונקציה getWeather() עם "Paris" כפרמטר מיקום.
2️⃣ הצ'אטבוט מפעיל את הפונקציה הזו בשם ה-LLM ומאחזר את התגובה של הפונקציה. בדוגמה הזו, נניח שהתשובה היא {"forecast": "sunny"}.

3️⃣ אפליקציית הצ'אטבוט שולחת את תגובת ה-JSON חזרה ל-LLM.

4️⃣ מודל ה-LLM בודק את תגובת ה-JSON, מפרש את המידע הזה ובסופו של דבר משיב עם הטקסט 'מזג האוויר בפריז הוא שמשי'.

כל שלב כקוד
קודם כל, מגדירים את מודל Gemini כרגיל:
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.maxOutputTokens(100)
.build();
מגדירים מפרט כלי שמתאר את הפונקציה שאפשר להפעיל:
ToolSpecification weatherToolSpec = ToolSpecification.builder()
.name("getWeather")
.description("Get the weather forecast for a given location or city")
.parameters(JsonObjectSchema.builder()
.addStringProperty(
"location",
"the location or city to get the weather forecast for")
.build())
.build();
השם של הפונקציה מוגדר, וגם השם והסוג של הפרמטר, אבל שימו לב שגם הפונקציה וגם הפרמטרים מקבלים תיאורים. התיאורים חשובים מאוד ועוזרים למודל שפה גדול (LLM) להבין מה פונקציה יכולה לעשות, וכך לשפוט אם צריך להפעיל את הפונקציה הזו בהקשר של השיחה.
נתחיל בשלב הראשון ונשלח את השאלה הראשונה על מזג האוויר בפריז:
List<ChatMessage> allMessages = new ArrayList<>();
// 1) Ask the question about the weather
UserMessage weatherQuestion = UserMessage.from("What is the weather in Paris?");
allMessages.add(weatherQuestion);
בשלב 2, אנחנו מעבירים את הכלי שבו אנחנו רוצים שהמודל ישתמש, והמודל משיב עם בקשה להפעלת הכלי:
// 2) The model replies with a function call request
Response<AiMessage> messageResponse = model.generate(allMessages, weatherToolSpec);
ToolExecutionRequest toolExecutionRequest = messageResponse.content().toolExecutionRequests().getFirst();
System.out.println("Tool execution request: " + toolExecutionRequest);
allMessages.add(messageResponse.content());
שלב 3. בשלב הזה, אנחנו יודעים לאיזו פונקציה מודל ה-LLM רוצה שנקרא. בקוד, אנחנו לא מבצעים קריאה אמיתית לממשק API חיצוני, אנחנו רק מחזירים תחזית מזג אוויר היפותטית ישירות:
// 3) We send back the result of the function call
ToolExecutionResultMessage toolExecResMsg = ToolExecutionResultMessage.from(toolExecutionRequest,
"{\"location\":\"Paris\",\"forecast\":\"sunny\", \"temperature\": 20}");
allMessages.add(toolExecResMsg);
בשלב 4, מודל ה-LLM לומד על תוצאת ההפעלה של הפונקציה, ואז יכול לסנתז תשובה טקסטואלית:
// 4) The model answers with a sentence describing the weather
Response<AiMessage> weatherResponse = model.generate(allMessages);
System.out.println("Answer: " + weatherResponse.content().text());
קוד המקור המלא נמצא ב-FunctionCalling.java בספרייה app/src/main/java/gemini/workshop.
מריצים את הדוגמה:
./gradlew run -q -DjavaMainClass=gemini.workshop.FunctionCalling
הפלט אמור להיראות כך:
Tool execution request: ToolExecutionRequest { id = null, name = "getWeatherForecast", arguments = "{"location":"Paris"}" }
Answer: The weather in Paris is sunny with a temperature of 20 degrees Celsius.
אפשר לראות בפלט שלמעלה את בקשת ההפעלה של הכלי, וגם את התשובה.
12. LangChain4j מטפל בקריאה להפעלת פונקציות
בשלב הקודם ראיתם איך האינטראקציות הרגילות של שאלות ותשובות בטקסט ושל בקשות ותגובות לפונקציות משולבות זו בזו, וביניהן סיפקתם את התגובה המבוקשת לפונקציה ישירות, בלי להפעיל פונקציה אמיתית.
עם זאת, LangChain4j מציעה גם הפשטה ברמה גבוהה יותר שיכולה לטפל בקריאות לפונקציות באופן שקוף בשבילכם, תוך כדי טיפול בשיחה כרגיל.
קריאה יחידה לפונקציה
בואו נבחן את FunctionCallingAssistant.java, חלק אחר חלק.
קודם כול, יוצרים רשומה שתייצג את מבנה נתוני התגובה של הפונקציה:
record WeatherForecast(String location, String forecast, int temperature) {}
התשובה מכילה מידע על המיקום, התחזית והטמפרטורה.
לאחר מכן יוצרים מחלקה שמכילה את הפונקציה בפועל שרוצים להפוך לזמינה למודל:
static class WeatherForecastService {
@Tool("Get the weather forecast for a location")
WeatherForecast getForecast(@P("Location to get the forecast for") String location) {
if (location.equals("Paris")) {
return new WeatherForecast("Paris", "Sunny", 20);
} else if (location.equals("London")) {
return new WeatherForecast("London", "Rainy", 15);
} else {
return new WeatherForecast("Unknown", "Unknown", 0);
}
}
}
שימו לב שהמחלקה הזו מכילה פונקציה אחת, אבל היא מסומנת בהערה @Tool שמתאימה לתיאור של הפונקציה שהמודל יכול לבקש להפעיל.
גם הפרמטרים של הפונקציה (יש כאן רק אחד) מסומנים בהערה, אבל עם ההערה הקצרה @P, שכוללת גם תיאור של הפרמטר. יכולתם להוסיף כמה פונקציות שרציתם, כדי שהמודל יוכל להשתמש בהן בתרחישים מורכבים יותר.
בשיעור הזה, מחזירים כמה תשובות מוכנות מראש, אבל אם רוצים להתקשר לשירות חיצוני אמיתי של תחזית מזג האוויר, צריך להוסיף את הקריאה לשירות הזה בגוף של ה-method.
כפי שראינו כשנוצר ToolSpecification בגישה הקודמת, חשוב לתעד מה הפונקציה עושה ולתאר למה הפרמטרים מתאימים. כך המודל מבין איך ומתי אפשר להשתמש בפונקציה הזו.
לאחר מכן, LangChain4j מאפשרת לכם לספק ממשק שמתאים לחוזה שבו אתם רוצים להשתמש כדי ליצור אינטראקציה עם המודל. במקרה הזה, זהו ממשק פשוט שמקבל מחרוזת שמייצגת את הודעת המשתמש ומחזיר מחרוזת שתואמת לתגובת המודל:
interface WeatherAssistant {
String chat(String userMessage);
}
אפשר גם להשתמש בחתימות מורכבות יותר שכוללות את UserMessage של LangChain4j (להודעת משתמש) או את AiMessage (לתגובת מודל), או אפילו את TokenStream, אם רוצים לטפל במצבים מתקדמים יותר, כי האובייקטים המורכבים האלה מכילים גם מידע נוסף כמו מספר הטוקנים שנצרכו וכו'. אבל כדי לפשט את הדברים, נשתמש רק במחרוזת כקלט ובמחרוזת כפלט.
נסיים עם שיטה main() שמאגדת את כל החלקים:
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.build();
WeatherForecastService weatherForecastService = new WeatherForecastService();
WeatherAssistant assistant = AiServices.builder(WeatherAssistant.class)
.chatLanguageModel(model)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.tools(weatherForecastService)
.build();
System.out.println(assistant.chat("What is the weather in Paris?"));
System.out.println(assistant.chat("Is it warmer in London or in Paris?"));
}
כמו תמיד, אתם מגדירים את מודל Gemini Chat. לאחר מכן יוצרים מופע של שירות תחזית מזג האוויר שמכיל את ה'פונקציה' שהמודל יבקש מאיתנו להפעיל.
עכשיו משתמשים שוב במחלקה AiServices כדי לקשור את מודל הצ'אט, את זיכרון הצ'אט ואת הכלי (כלומר, שירות תחזית מזג האוויר עם הפונקציה שלו). AiServices מחזירה אובייקט שמיישם את הממשק WeatherAssistant שהגדרתם. הדבר היחיד שנותר הוא לקרוא ל-method chat() של העוזר הדיגיטלי. כשמפעילים את התכונה, רואים רק את תשובות הטקסט, אבל הבקשות להפעלת הפונקציה והתשובות להפעלת הפונקציה לא מוצגות למפתח. הבקשות האלה מטופלות באופן אוטומטי ושקוף. אם Gemini יחשוב שצריך להפעיל פונקציה, הוא ישיב עם בקשה להפעלת הפונקציה, ו-LangChain4j יפעיל את הפונקציה המקומית בשמכם.
מריצים את הדוגמה:
./gradlew run -q -DjavaMainClass=gemini.workshop.FunctionCallingAssistant
הפלט אמור להיראות כך:
OK. The weather in Paris is sunny with a temperature of 20 degrees.
It is warmer in Paris (20 degrees) than in London (15 degrees).
זו הייתה דוגמה לפונקציה אחת.
קריאות לכמה פונקציות
אפשר גם להשתמש בכמה פונקציות ולאפשר ל-LangChain4j לטפל בכמה קריאות לפונקציות בשמכם. MultiFunctionCallingAssistant.java מציגה דוגמה לשימוש בכמה פונקציות.
יש לה פונקציה להמרת מטבעות:
@Tool("Convert amounts between two currencies")
double convertCurrency(
@P("Currency to convert from") String fromCurrency,
@P("Currency to convert to") String toCurrency,
@P("Amount to convert") double amount) {
double result = amount;
if (fromCurrency.equals("USD") && toCurrency.equals("EUR")) {
result = amount * 0.93;
} else if (fromCurrency.equals("USD") && toCurrency.equals("GBP")) {
result = amount * 0.79;
}
System.out.println(
"convertCurrency(fromCurrency = " + fromCurrency +
", toCurrency = " + toCurrency +
", amount = " + amount + ") == " + result);
return result;
}
פונקציה נוספת לקבלת ערך של מניה:
@Tool("Get the current value of a stock in US dollars")
double getStockPrice(@P("Stock symbol") String symbol) {
double result = 170.0 + 10 * new Random().nextDouble();
System.out.println("getStockPrice(symbol = " + symbol + ") == " + result);
return result;
}
פונקציה נוספת להחלת אחוז על סכום נתון:
@Tool("Apply a percentage to a given amount")
double applyPercentage(@P("Initial amount") double amount, @P("Percentage between 0-100 to apply") double percentage) {
double result = amount * (percentage / 100);
System.out.println("applyPercentage(amount = " + amount + ", percentage = " + percentage + ") == " + result);
return result;
}
אחר כך אפשר לשלב את כל הפונקציות האלה עם מחלקה של MultiTools ולשאול שאלות כמו "מהו מחיר המניה של AAPL ב-10% אחרי המרה מדולר אמריקאי לאירו?"
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.maxOutputTokens(100)
.build();
MultiTools multiTools = new MultiTools();
MultiToolsAssistant assistant = AiServices.builder(MultiToolsAssistant.class)
.chatLanguageModel(model)
.chatMemory(withMaxMessages(10))
.tools(multiTools)
.build();
System.out.println(assistant.chat(
"What is 10% of the AAPL stock price converted from USD to EUR?"));
}
מריצים אותה כך:
./gradlew run -q -DjavaMainClass=gemini.workshop.MultiFunctionCallingAssistant
אפשר לראות את הפונקציות השונות שהופעלו:
getStockPrice(symbol = AAPL) == 172.8022224055534 convertCurrency(fromCurrency = USD, toCurrency = EUR, amount = 172.8022224055534) == 160.70606683716468 applyPercentage(amount = 160.70606683716468, percentage = 10.0) == 16.07060668371647 10% of the AAPL stock price converted from USD to EUR is 16.07060668371647 EUR.
לנציגים
התקשרות לפונקציה היא מנגנון הרחבה מצוין למודלים גדולים של שפה כמו Gemini. הטכנולוגיה הזו מאפשרת לנו לבנות מערכות מורכבות יותר, שלרוב נקראות 'סוכנים' או 'עוזרים מבוססי-AI'. הסוכנים האלה יכולים ליצור אינטראקציה עם העולם החיצוני באמצעות ממשקי API חיצוניים ועם שירותים שיכולים להשפיע על הסביבה החיצונית (למשל, שליחת אימיילים, יצירת כרטיסים וכו').
כשיוצרים סוכנים כל כך חזקים, חשוב לעשות זאת באחריות. לפני שמבצעים פעולות אוטומטיות, כדאי לשקול להשתמש בבודק אנושי. חשוב לשמור על בטיחות כשמעצבים סוכנים מבוססי-LLM שמבצעים אינטראקציה עם העולם החיצוני.
13. הרצת Gemma באמצעות Ollama ו-TestContainers
עד עכשיו השתמשנו ב-Gemini, אבל יש גם את Gemma, המודל הקטן יותר.
Gemma היא משפחה של מודלים קלי משקל ומתקדמים בקוד פתוח, שנבנו על בסיס אותם מחקרים וטכנולוגיות ששימשו ליצירת המודלים של Gemini. המודל העדכני ביותר של Gemma הוא Gemma3, שזמין בארבעה גדלים: 1B (טקסט בלבד), 4B, 12B ו-27B. המשקלים שלהם זמינים באופן חופשי, והגודל הקטן שלהם מאפשר להריץ אותם בעצמכם, אפילו במחשב הנייד או ב-Cloud Shell.
איך מריצים את Gemma?
יש הרבה דרכים להריץ את Gemma: בענן, דרך Vertex AI בלחיצת כפתור, או GKE עם כמה GPUs, אבל אפשר גם להריץ אותו באופן מקומי.
אחת האפשרויות הטובות להרצת Gemma באופן מקומי היא באמצעות Ollama, כלי שמאפשר להריץ מודלים קטנים, כמו Llama, Mistral ועוד רבים אחרים במחשב המקומי. הוא דומה ל-Docker אבל מיועד ל-LLM.
מתקינים את Ollama לפי ההוראות למערכת ההפעלה שלכם.
אם אתם משתמשים בסביבת Linux, תצטרכו להפעיל את Ollama אחרי ההתקנה.
ollama serve > /dev/null 2>&1 &
אחרי ההתקנה המקומית, אפשר להריץ פקודות כדי לשלוף מודל:
ollama pull gemma3:1b
מחכים שהמודל יימשך. פעולה זו עשויה להימשך זמן מה.
מריצים את המודל:
ollama run gemma3:1b
עכשיו אפשר לתקשר עם המודל:
>>> Hello! Hello! It's nice to hear from you. What can I do for you today?
כדי לצאת מההנחיה, מקישים על Ctrl+D.
הפעלת Gemma ב-Ollama ב-TestContainers
במקום להתקין ולהריץ את Ollama באופן מקומי, אפשר להשתמש ב-Ollama בתוך קונטיינר שמנוהל על ידי TestContainers.
TestContainers לא שימושי רק לבדיקות, אלא גם להרצת קונטיינרים. יש אפילו OllamaContainer ספציפי שתוכלו לנצל!
הנה התמונה המלאה:
הטמעה
בואו נבחן את GemmaWithOllamaContainer.java, חלק אחר חלק.
קודם צריך ליצור קובץ Ollama נגזר ששולף את מודל Gemma. התמונה הזו כבר קיימת מהרצה קודמת או שהיא תיווצר. אם התמונה כבר קיימת, צריך רק להגיד ל-TestContainers שרוצים להחליף את תמונת ברירת המחדל של Ollama בגרסה מבוססת-Gemma:
private static final String TC_OLLAMA_GEMMA3 = "tc-ollama-gemma3-1b";
public static final String GEMMA_3 = "gemma3:1b";
// Creating an Ollama container with Gemma 3 if it doesn't exist.
private static OllamaContainer createGemmaOllamaContainer() throws IOException, InterruptedException {
// Check if the custom Gemma Ollama image exists already
List<Image> listImagesCmd = DockerClientFactory.lazyClient()
.listImagesCmd()
.withImageNameFilter(TC_OLLAMA_GEMMA3)
.exec();
if (listImagesCmd.isEmpty()) {
System.out.println("Creating a new Ollama container with Gemma 3 image...");
OllamaContainer ollama = new OllamaContainer("ollama/ollama:0.7.1");
System.out.println("Starting Ollama...");
ollama.start();
System.out.println("Pulling model...");
ollama.execInContainer("ollama", "pull", GEMMA_3);
System.out.println("Committing to image...");
ollama.commitToImage(TC_OLLAMA_GEMMA3);
return ollama;
}
System.out.println("Ollama image substitution...");
// Substitute the default Ollama image with our Gemma variant
return new OllamaContainer(
DockerImageName.parse(TC_OLLAMA_GEMMA3)
.asCompatibleSubstituteFor("ollama/ollama"));
}
לאחר מכן יוצרים ומתחילים קונטיינר בדיקה של Ollama, ואז יוצרים מודל צ'אט של Ollama, על ידי הפניה לכתובת ולפורט של הקונטיינר עם המודל שרוצים להשתמש בו. לבסוף, מפעילים את model.generate(yourPrompt) כרגיל:
public static void main(String[] args) throws IOException, InterruptedException {
OllamaContainer ollama = createGemmaOllamaContainer();
ollama.start();
ChatLanguageModel model = OllamaChatModel.builder()
.baseUrl(String.format("http://%s:%d", ollama.getHost(), ollama.getFirstMappedPort()))
.modelName(GEMMA_3)
.build();
String response = model.generate("Why is the sky blue?");
System.out.println(response);
}
מריצים אותה כך:
./gradlew run -q -DjavaMainClass=gemini.workshop.GemmaWithOllamaContainer
ההרצה הראשונה תיקח זמן כי צריך ליצור ולהריץ את הקונטיינר, אבל בסיום התהליך אמורה להתקבל תגובה מ-Gemma:
INFO: Container ollama/ollama:0.7.1 started in PT7.228339916S
The sky appears blue due to Rayleigh scattering. Rayleigh scattering is a phenomenon that occurs when sunlight interacts with molecules in the Earth's atmosphere.
* **Scattering particles:** The main scattering particles in the atmosphere are molecules of nitrogen (N2) and oxygen (O2).
* **Wavelength of light:** Blue light has a shorter wavelength than other colors of light, such as red and yellow.
* **Scattering process:** When blue light interacts with these molecules, it is scattered in all directions.
* **Human eyes:** Our eyes are more sensitive to blue light than other colors, so we perceive the sky as blue.
This scattering process results in a blue appearance for the sky, even though the sun is actually emitting light of all colors.
In addition to Rayleigh scattering, other atmospheric factors can also influence the color of the sky, such as dust particles, aerosols, and clouds.
הפעלתם את Gemma ב-Cloud Shell.
14. מזל טוב
מעולה, יצרתם בהצלחה את אפליקציית הצ'אט הראשונה שלכם מבוססת-AI גנרטיבי ב-Java באמצעות LangChain4j ו-Gemini API. במהלך הדרך גיליתם שמודלים גדולים של שפה (LLM) מולטי-מודאליים הם די חזקים ויכולים לבצע משימות שונות כמו מענה על שאלות, גם במסמכים שלכם, חילוץ נתונים, אינטראקציה עם ממשקי API חיצוניים ועוד.
מה השלב הבא?
עכשיו תורכם לשפר את האפליקציות שלכם באמצעות שילובים עוצמתיים של LLM!
קריאה נוספת
- תרחישים נפוצים לשימוש ב-AI גנרטיבי
- מקורות מידע להדרכה בנושא AI גנרטיבי
- איך מתקשרים עם Gemini באמצעות Generative AI Studio
- אתיקה של בינה מלאכותית