Vertex AI ve LangChain4j ile Java'da Gemini

1. Giriş

Bu kod laboratuvarında, Google Cloud'daki Vertex AI'da barındırılan Gemini Büyük Dil Modeli (LLM) ele alınmaktadır. Vertex AI, Google Cloud'daki tüm makine öğrenimi ürünlerini, hizmetlerini ve modellerini kapsayan bir platformdur.

LangChain4j çerçevesini kullanarak Gemini API ile etkileşim kurmak için Java'yı kullanacaksınız. Soru yanıtlama, fikir üretme, öğe ve yapılandırılmış içerik ayıklama, aramayla desteklenen üretme ve işlev çağırma için LLM'den yararlanmak üzere somut örnekleri inceleyeceksiniz.

Üretken yapay zeka nedir?

Üretken yapay zeka, metin, resim, müzik, ses ve video gibi yeni içerikler oluşturmak için yapay zekanın kullanılmasını ifade eder.

Üretken yapay zeka, birden fazla görevi aynı anda gerçekleştirebilen ve özetleme, soru-cevap, sınıflandırma gibi hazır görevler yapabilen büyük dil modellerinden (LLM'ler) güç alır. Temel modeller, çok az örnek veri kullanılarak minimum düzeyde eğitimle hedeflenen kullanım alanlarına uyarlanabilir.

Üretken yapay zeka nasıl çalışır?

Üretken yapay zeka, insan tarafından oluşturulan içeriklerden oluşan bir veri kümesindeki kalıpları ve ilişkileri öğrenmek için bir makine öğrenimi (ML) modeli kullanır. Ardından, öğrendiği kalıpları kullanarak yeni içerikler oluşturur.

Üretken yapay zeka modelini eğitmenin en yaygın yolu, gözetimli öğrenmeyi kullanmaktır. Modele, gerçek kişiler tarafından oluşturulan bir içerik grubu ve ilgili etiketler verilir. Ardından, insan tarafından oluşturulan içeriklere benzer içerikler üretmeyi öğrenir.

Üretken yapay zekanın yaygın uygulamaları nelerdir?

Üretken yapay zeka şu amaçlarla kullanılabilir:

  • Gelişmiş sohbet ve arama deneyimleri sayesinde müşteri etkileşimlerini iyileştirin.
  • Konuşma arayüzleri ve özetler aracılığıyla çok sayıda yapılandırılmamış veriyi keşfedin.
  • Teklif isteklerini yanıtlama, pazarlama içeriğini farklı dillerde yerelleştirme ve müşteri sözleşmelerinin uygunluğunu kontrol etme gibi tekrarlanan görevlerde yardımcı olur.

Google Cloud'da hangi üretken yapay zeka teklifleri mevcut?

Vertex AI ile, çok az veya hiç makine öğrenimi uzmanlığı olmadan temel modellerle etkileşim kurabilir, bunları özelleştirebilir ve uygulamalarınıza yerleştirebilirsiniz. Model Garden'da temel modellere erişebilir, Vertex AI Studio'daki basit bir kullanıcı arayüzü üzerinden modelleri ayarlayabilir veya modelleri bir veri bilimi not defterinde kullanabilirsiniz.

Vertex AI Arama ve Sohbet, geliştiricilere üretken yapay zeka destekli arama motorları ve chatbot'lar oluşturmanın en hızlı yolunu sunar.

Gemini tarafından desteklenen Google Cloud için Gemini, daha fazla işi daha hızlı yapmanıza yardımcı olmak için Google Cloud ve IDE'lerde kullanılabilen yapay zeka destekli bir ortak çalışma aracıdır. Gemini Code Assist, kod tamamlama, kod oluşturma, kod açıklamaları sağlar ve teknik sorular sormak için onunla sohbet etmenize olanak tanır.

Gemini nedir?

Gemini, Google DeepMind tarafından geliştirilen ve çok formatlı kullanım alanları için tasarlanmış bir üretken yapay zeka modeli ailesidir. Çok modlu olması, metin, kod, resim ve ses gibi farklı içerik türlerini işleyip oluşturabileceği anlamına gelir.

b9913d011999e7c7.png

Gemini farklı varyant ve boyutlarda sunulur:

  • Gemini Ultra: Karmaşık görevler için en büyük ve en yetenekli sürümdür.
  • Gemini Flash: Yüksek hacimli görevler için optimize edilmiş, en hızlı ve en uygun maliyetli modeldir.
  • Gemini Pro: Orta boyutlu, çeşitli görevlerde ölçeklendirme için optimize edilmiştir.
  • Gemini Nano: Cihaz üzerindeki görevler için tasarlanmış en verimli modeldir.

Temel Özellikler:

  • Çoklu modalite: Gemini'nin birden fazla bilgi biçimini anlayıp işleyebilmesi, yalnızca metin tabanlı geleneksel dil modellerinin ötesinde önemli bir adımdır.
  • Performans: Gemini Ultra, birçok karşılaştırma testinde mevcut en gelişmiş modellerden daha iyi performans gösterir ve zorlu MMLU (Massive Multitask Language Understanding) karşılaştırma testinde insan uzmanları aşan ilk modeldir.
  • Esneklik: Farklı Gemini boyutları, büyük ölçekli araştırmalardan mobil cihazlarda dağıtıma kadar çeşitli kullanım alanlarına uyum sağlar.

Java'dan Vertex AI'daki Gemini ile nasıl etkileşim kurabilirsiniz?

Bunun için iki seçeneğiniz bulunmaktadır:

  1. Resmi Gemini için Vertex AI Java API kitaplığı.
  2. LangChain4j çerçevesi.

Bu codelab'de LangChain4j çerçevesini kullanacaksınız.

LangChain4j çerçevesi nedir?

LangChain4j çerçevesi, LLM'nin kendisi gibi çeşitli bileşenleri ve vektör veritabanları (anlamsal aramalar için), doküman yükleyiciler ve bölücüler (dokümanları analiz etmek ve onlardan bilgi edinmek için), çıkış ayrıştırıcılar gibi diğer araçları koordine ederek LLM'leri Java uygulamalarınıza entegre etmek için kullanılan açık kaynak bir kitaplıktır.

Proje, LangChain Python projesinden esinlenerek Java geliştiricilerine hizmet vermek amacıyla oluşturuldu.

bb908ea1e6c96ac2.png

Neler öğreneceksiniz?

  • Gemini ve LangChain4j'i kullanacak bir Java projesi oluşturma
  • İlk isteminizi Gemini'ye programatik olarak gönderme
  • Gemini'den gelen yanıtları akış şeklinde gösterme
  • Kullanıcı ile Gemini arasında sohbet oluşturma
  • Hem metin hem de resim göndererek Gemini'yi çoklu formatlı bağlamda kullanma
  • Yapılandırılmamış içerikten yararlı yapılandırılmış bilgiler ayıklama
  • İstem şablonlarını değiştirme
  • Yaklaşım analizi gibi metin sınıflandırması yapma
  • Kendi dokümanlarınızla sohbet etme (Almayla Artırılmış Üretim)
  • Chatbot'larınızı işlev çağrısıyla genişletme
  • Gemma'yı Ollama ve TestContainers ile yerel olarak kullanma

Gerekenler

  • Java programlama dili hakkında bilgi
  • Google Cloud projesi
  • Chrome veya Firefox gibi bir tarayıcı

2. Kurulum ve şartlar

Kendine ait tempoda ortam oluşturma

  1. Google Cloud Console'da oturum açın ve yeni bir proje oluşturun veya mevcut bir projeyi yeniden kullanın. Gmail veya Google Workspace hesabınız yoksa hesap oluşturmanız gerekir.

fbef9caa1602edd0.png

a99b7ace416376c4.png

5e3ff691252acf41.png

  • Proje adı, bu projenin katılımcılarının görünen adıdır. Google API'leri tarafından kullanılmayan bir karakter dizesidir. Dilediğiniz zaman güncelleyebilirsiniz.
  • Proje kimliği, tüm Google Cloud projelerinde benzersizdir ve değiştirilemez (ayarlandıktan sonra değiştirilemez). Cloud Console, benzersiz bir dize otomatik olarak oluşturur. Bu dizenin ne olduğu genellikle önemli değildir. Çoğu kod laboratuvarında proje kimliğinize (genellikle PROJECT_ID olarak tanımlanır) referans vermeniz gerekir. Oluşturulan kimliği beğenmezseniz rastgele başka bir kimlik oluşturabilirsiniz. Alternatif olarak, kendi anahtarınızı deneyerek kullanılabilir olup olmadığını görebilirsiniz. Bu adımdan sonra değiştirilemez ve proje boyunca geçerli kalır.
  • Bazı API'lerin kullandığı üçüncü bir değer (Proje Numarası) olduğunu belirtmek isteriz. Bu üç değer hakkında daha fazla bilgiyi dokümanlar bölümünde bulabilirsiniz.
  1. Ardından, Cloud kaynaklarını/API'lerini kullanmak için Cloud Console'da faturalandırmayı etkinleştirmeniz gerekir. Bu codelab'i çalıştırmak çok pahalı değildir. Bu eğitimden sonra faturalandırılmamak için kaynakları kapatmak istiyorsanız oluşturduğunuz kaynakları veya projeyi silebilirsiniz. Yeni Google Cloud kullanıcıları 300 ABD doları değerindeki ücretsiz deneme programına uygundur.

Cloud Shell'i başlatma

Google Cloud, dizüstü bilgisayarınızdan uzaktan çalıştırılabilir olsa da bu kod laboratuvarında bulutta çalışan bir komut satırı ortamı olan Cloud Shell'i kullanacaksınız.

Cloud Shell'i etkinleştirme

  1. Cloud Console'da Cloud Shell'i etkinleştir 'i 853e55310c205094.png tıklayın.

3c1dabeca90e44e5.png

Cloud Shell'i ilk kez başlatıyorsanız Cloud Shell'in ne olduğunu açıklayan bir ara ekran gösterilir. Ara ekran gösterildiyse Devam'ı tıklayın.

9c92662c6a846a5c.png

Cloud Shell'e bağlanmak ve ortam oluşturmak yalnızca birkaç dakikanızı alır.

9f0e51b578fecce5.png

Bu sanal makinede, ihtiyaç duyulan tüm geliştirme araçları yüklüdür. 5 GB boyutunda kalıcı bir ana dizin sunar ve Google Cloud'da çalışır. Bu sayede ağ performansını ve kimlik doğrulamayı büyük ölçüde iyileştirir. Bu kod laboratuvarındaki çalışmanızın tamamı olmasa da büyük bir kısmı tarayıcıda yapılabilir.

Cloud Shell'e bağlandıktan sonra kimliğinizin doğrulandığını ve projenin proje kimliğinize ayarlandığını görürsünüz.

  1. Kimliğinizi doğrulamak için Cloud Shell'de aşağıdaki komutu çalıştırın:
gcloud auth list

Komut çıkışı

 Credentialed Accounts
ACTIVE  ACCOUNT
*       <my_account>@<my_domain.com>

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
  1. gcloud komutunun projeniz hakkında bilgi sahibi olduğunu onaylamak için Cloud Shell'de aşağıdaki komutu çalıştırın:
gcloud config list project

Komut çıkışı

[core]
project = <PROJECT_ID>

Aksi takdirde aşağıdaki komutla ayarlayabilirsiniz:

gcloud config set project <PROJECT_ID>

Komut çıkışı

Updated property [core/project].

3. Geliştirme ortamınızı hazırlama

Bu codelab'de, Java programlarınızı geliştirmek için Cloud Shell terminalini ve Cloud Shell düzenleyicisini kullanacaksınız.

Vertex AI API'lerini etkinleştirme

Google Cloud Console'da, proje adınızın Google Cloud Console'un üst kısmında gösterildiğinden emin olun. Aksi takdirde, Proje Seçici'yi açmak için Proje seç'i tıklayın ve istediğiniz projeyi seçin.

Vertex AI API'lerini Google Cloud Console'un Vertex AI bölümünden veya Cloud Shell terminalinden etkinleştirebilirsiniz.

Google Cloud Console'dan etkinleştirmek için önce Google Cloud Console menüsünün Vertex AI bölümüne gidin:

451976f1c8652341.png

Vertex AI kontrol panelinde Önerilen Tüm API'leri Etkinleştir'i tıklayın.

Bu işlemle birkaç API etkinleştirilir ancak codelab için en önemli API aiplatform.googleapis.com'tür.

Alternatif olarak, aşağıdaki komutu kullanarak Cloud Shell terminalinden de bu API'yi etkinleştirebilirsiniz:

gcloud services enable aiplatform.googleapis.com

GitHub deposunu klonlayın.

Cloud Shell terminalinde bu kod laboratuvarının deposunu klonlayın:

git clone https://github.com/glaforge/gemini-workshop-for-java-developers.git

Projenin çalışmaya hazır olup olmadığını kontrol etmek için "Merhaba Dünya" programını çalıştırmayı deneyebilirsiniz.

Üst düzey klasörde olduğunuzdan emin olun:

cd gemini-workshop-for-java-developers/ 

Gradle sarmalayıcısını oluşturun:

gradle wrapper

gradlew ile çalıştırın:

./gradlew run

Aşağıdaki çıkışı göreceksiniz:

..
> Task :app:run
Hello World!

Cloud Editor'ı açma ve ayarlama

Cloud Shell'den Cloud Code Düzenleyici ile kodu açın:

42908e11b28f4383.png

Cloud Code Editor'da File -> Open Folder'ü seçerek codelab kaynak klasörünü açın ve codelab kaynak klasörüne (ör. /home/username/gemini-workshop-for-java-developers/).

Ortam değişkenlerini ayarlama

Terminal -> New Terminal'ü seçerek Cloud Code Editor'da yeni bir terminal açın. Kod örneklerini çalıştırmak için gereken iki ortam değişkenini ayarlayın:

  • PROJECT_ID: Google Cloud proje kimliğiniz
  • LOCATION: Gemini modelinin dağıtıldığı bölge

Değişkenleri aşağıdaki şekilde dışa aktarın:

export PROJECT_ID=$(gcloud config get-value project)
export LOCATION=us-central1

4. Gemini modeline ilk çağrı

Proje düzgün bir şekilde ayarlandığına göre Gemini API'yi çağırmanın zamanı geldi.

app/src/main/java/gemini/workshop dizininde QA.java dosyasına göz atın:

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?"));
    }
}

Bu ilk örnekte, ChatModel arayüzünü uygulayan VertexAiGeminiChatModel sınıfını içe aktarmanız gerekir.

main yönteminde, VertexAiGeminiChatModel için oluşturucuyu kullanarak sohbet dil modelini yapılandırır ve aşağıdakileri belirtirsiniz:

  • Proje
  • Konum
  • Model adı (gemini-1.5-flash-002).

Dil modeli hazır olduğunda generate() yöntemini çağırabilir ve isteminizi, sorunuzu veya LLM'ye gönderilecek talimatlarınızı iletebilirsiniz. Burada, gökyüzünün neden mavi olduğuyla ilgili basit bir soru soruyorsunuz.

Farklı sorular veya görevler denemek için bu istemi değiştirebilirsiniz.

Örneği kaynak kodunun kök klasöründe çalıştırın:

./gradlew run -q -DjavaMainClass=gemini.workshop.QA

Aşağıdakine benzer bir çıkış görürsünüz:

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.

Tebrikler, Gemini ile ilk görüşmenizi yaptınız.

Yanıtı akış şeklinde gösterme

Yanıtın birkaç saniye sonra tek seferde verildiğini fark ettiniz mi? Yanıtın akış yanıtı varyantı sayesinde kademeli olarak alınması da mümkündür. Akış yanıtı: Model, yanıtı kullanılabilir hale geldikçe parça parça döndürür.

Bu codelab'de akış şeklinde olmayan yanıtı kullanacağız ancak nasıl yapıldığını görmek için akış şeklinde yanıta da göz atalım.

app/src/main/java/gemini/workshop dizininde StreamQA.java altında akış yanıtını çalışırken görebilirsiniz:

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));
    }
}

Bu kez, StreamingChatLanguageModel arayüzünü uygulayan VertexAiGeminiStreamingChatModel akış sınıfı varyantlarını içe aktarıyoruz. Ayrıca, Java lambda ifadeleriyle bir akış işleyici oluşturmak için StreamingResponseHandler sağlayan bir kolaylık yöntemi olan LambdaStreamingResponseHandler.onNext sınıfını statik olarak içe aktarmanız gerekir.

Bu sefer generate() yönteminin imzası biraz farklı. Döndürülen tür, dize yerine void olur. İstemle birlikte bir canlı yayın yanıt işleyicisi de göndermeniz gerekir. Burada, yukarıda bahsettiğimiz statik içe aktarma sayesinde onNext() yöntemine ileteceğiniz bir lambda ifadesi tanımlayabiliriz. Lambda ifadesi, yanıtın yeni bir parçası her kullanılabilir olduğunda çağrılır. İkincisi ise yalnızca bir hata oluştuğunda çağrılır.

Çalıştırın:

./gradlew run -q -DjavaMainClass=gemini.workshop.StreamQA

Önceki sınıfa benzer bir yanıt alırsınız ancak bu kez, yanıtın tamamının gösterilmesini beklemek yerine yanıtın kabuğunuzda kademeli olarak göründüğünü fark edersiniz.

Ek yapılandırma

Yapılandırma için yalnızca projeyi, konumu ve model adını tanımladık ancak model için belirtebileceğiniz başka parametreler de vardır:

  • temperature(Float temp): Yanıtın ne kadar yaratıcı olmasını istediğinizi belirlemek için (0, düşük düzeyde yaratıcı ve genellikle daha gerçekçi, 2 ise daha yaratıcı sonuçlar içindir)
  • topP(Float topP): Toplam olasılığı bu kayan noktalı sayıya (0 ile 1 arasında) eşit olan olası kelimeleri seçmek için
  • topK(Integer topK): Metin tamamlama için olası maksimum sayıda kelimeden (1 ila 40) rastgele bir kelime seçmek
  • maxOutputTokens(Integer max): Model tarafından verilen yanıtın maksimum uzunluğunu belirtmek için (genellikle 4 jeton yaklaşık 3 kelimeyi temsil eder)
  • maxRetries(Integer retries): Zaman başına istek kotasını aşmanız veya platformun teknik bir sorunla karşı karşıya kalması durumunda modelin aramayı 3 kez yeniden denemesini sağlayabilirsiniz.

Şimdiye kadar Gemini'ye tek bir soru sordunuz ancak birden fazla soru içeren bir sohbet de yapabilirsiniz. Sonraki bölümde bu konuyu inceleyeceğiz.

5. Gemini ile sohbet edin

Önceki adımda tek bir soru sormuştunuz. Artık kullanıcı ile LLM arasında gerçek bir sohbet gerçekleştirme zamanı. Her soru ve yanıt, gerçek bir tartışma oluşturmak için öncekilerden yararlanabilir.

app/src/main/java/gemini/workshop klasöründeki Conversation.java dosyasına göz atın:

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));
        });
    }
}

Bu sınıfta birkaç yeni ve ilgi çekici içe aktarma işlemi yapıldı:

  • MessageWindowChatMemory: Sohbetin çok turlu yönünü yönetmeye ve önceki soruları ve yanıtları yerel bellekte tutmaya yardımcı olan bir sınıf
  • AiServices: Sohbet modelini ve sohbet belleğini birbirine bağlayan daha yüksek düzeyli bir soyutlama sınıfı

Ana yöntemde modeli, sohbet belleğini ve yapay zeka hizmetini ayarlayacaksınız. Model, proje, konum ve model adı bilgileriyle her zamanki gibi yapılandırılır.

Sohbet anısı için, MessageWindowChatMemory'ın oluşturucusunu kullanarak son 20 mesajın saklandığı bir anı oluştururuz. Bağlamı Java sınıfı istemcimizde yerel olarak tutulan görüşmenin kaydırma penceresidir.

Ardından, sohbet modelini sohbet belleğine bağlayan AI service öğesini oluşturursunuz.

Yapay zeka hizmetinin, LangChain4j tarafından uygulanan ve String sorgusu alıp String yanıtı döndüren, tanımladığımız özel bir ConversationService arayüzünü nasıl kullandığını fark edin.

Şimdi Gemini ile sohbet etme zamanı. Önce basit bir karşılama mesajı gönderilir, ardından Eyfel Kulesi'nin hangi ülkede olduğunu öğrenmek için ilk soru sorulur. Son cümlenin, Eyfel Kulesi'nin bulunduğu ülkede kaç kişinin yaşadığını merak ettiğiniz için ilk sorunun cevabıyla ilgili olduğunu fark edin. Bu cümlede, önceki yanıtta verilen ülkeden açıkça bahsedilmiyor. Bu, her istemle birlikte geçmiş sorular ve yanıtların gönderildiğini gösterir.

Örneği çalıştırın:

./gradlew run -q -DjavaMainClass=gemini.workshop.Conversation

Aşağıdakilere benzer üç yanıt görürsünüz:

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 ile tek katılımlı sorular sorabilir veya çok katılımlı sohbetler yapabilirsiniz. Ancak şu ana kadar yalnızca metin girişi kabul edilmektedir. Resimler ne olacak? Sonraki adımda resimleri inceleyelim.

6. Gemini ile çok formatlılık

Gemini, çok formatlı bir modeldir. Giriş olarak yalnızca metin değil, resim ve hatta video da kabul eder. Bu bölümde, metin ve resimleri karıştırmaya yönelik bir kullanım alanı göreceksiniz.

Gemini'nin bu kediyi tanıyacağını düşünüyor musunuz?

af00516493ec9ade.png

Wikipedia'dan alınan, kardaki bir kedinin resmihttps://upload.wikimedia.org/wikipedia/commons/b/b6/Felis_catus-cat_on_snow.jpg

app/src/main/java/gemini/workshop dizininde Multimodal.java dosyasına göz atın:

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());
    }
}

İçe aktarma işlemlerinde farklı mesaj ve içerik türlerini ayırt ettiğimizi unutmayın. UserMessage hem TextContent hem de ImageContent nesnesi içerebilir. Bu, metin ve görselleri bir araya getiren çok formatlı bir yaklaşımdır. Yalnızca basit bir dize istemi göndermeyiz. Kullanıcı mesajını temsil eden, resim içeriği parçası ve metin içeriği parçasından oluşan daha yapılandırılmış bir nesne göndeririz. Model, bir AiMessage içeren bir Response döndürür.

Ardından, content() aracılığıyla yanıttan AiMessage'yi, text() sayesinde de mesaj metnini alırsınız.

Örneği çalıştırın:

./gradlew run -q -DjavaMainClass=gemini.workshop.Multimodal

Resmin adı, resimde ne olduğuna dair bir ipucu veriyordu ancak Gemini'nin verdiği sonuç aşağıdakine benzer:

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.

Resim ve metin istemlerini karıştırmak ilginç kullanım alanlarına olanak tanır. Şunları yapabilen uygulamalar oluşturabilirsiniz:

  • Resimlerdeki metinleri tanıyabilir.
  • Bir resmin gösterilmesinin güvenli olup olmadığını kontrol etme
  • Resim altyazılarını oluşturabilirsiniz.
  • Düz metin açıklamaları içeren bir resim veritabanında arama yapın.

Görüntülerden bilgi ayıklamanın yanı sıra, yapılandırılmamış metinlerden de bilgi ayıklayabilirsiniz. Bunu bir sonraki bölümde öğreneceksiniz.

7. Yapılandırılmamış metinden yapılandırılmış bilgiler ayıklama

Rapor dokümanlarındaki, e-postalardaki veya diğer uzun metinlerdeki önemli bilgilerin yapılandırılmamış bir şekilde verildiği birçok durum vardır. İdeal olarak, yapılandırılmamış metinde bulunan temel ayrıntıları yapılandırılmış nesneler biçiminde ayıklayabilmeniz gerekir. Bunu nasıl yapabileceğinize bakalım.

Bir kişinin biyografisi, özgeçmişi veya açıklaması verildiğinde kişinin adını ve yaşını ayıklamak istediğinizi varsayalım. LLM'ye, akıllıca düzenlenmiş bir istemle (buna genellikle "istem mühendisliği" denir) yapılandırılmamış metinden JSON ayıklaması talimatı verebilirsiniz.

Ancak aşağıdaki örnekte, JSON çıkışını açıklayan bir istem oluşturmak yerine Gemini'nin yapılandırılmış çıkış adlı güçlü bir özelliğini kullanacağız. Bu özellik, modeli belirli bir JSON şemasına uygun olarak yalnızca geçerli JSON içeriği yayınlamaya zorlayan, bazen de kısıtlanmış kod çözme olarak adlandırılır.

app/src/main/java/gemini/workshop'daki ExtractData.java bölümüne göz atın:

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
    }
}

Bu dosyanın çeşitli adımlarına göz atalım:

  • Person kaydı, bir kişiyi tanımlayan ayrıntıları (ad ve yaş) temsil etmek için tanımlanır.
  • PersonExtractor arayüzü, yapılandırılmamış bir metin dizesi verildiğinde Person örneği döndüren bir yöntemle tanımlanır.
  • extractPerson(), bir talimat istemi ile ilişkilendiren bir @SystemMessage ek açıklamasıyla ek açıklamaya sahiptir. Bu, modelin bilgileri ayıklamak için kullanacağı istemdir ve ayrıntıları JSON belgesi biçiminde döndürür. Bu belge sizin için ayrıştırılır ve bir Person örneğine ayrıştırılır.

Şimdi main() yönteminin içeriğine bakalım:

  • Sohbet modeli yapılandırılır ve örneklenir. Model oluşturucu sınıfının 2 yeni yöntemini kullanıyoruz: responseMimeType() ve responseSchema(). İlki, Gemini'ye çıkışta geçerli JSON oluşturmasını söyler. İkinci yöntem, döndürülmesi gereken JSON nesnesinin şemasını tanımlar. Ayrıca, ikincisi bir Java sınıfını veya kaydını uygun bir JSON şemasına dönüştürebilen bir kolaylık yöntemine yetki verir.
  • LangChain4j'ın AiServices sınıfı sayesinde bir PersonExtractor nesnesi oluşturulur.
  • Ardından, kişiyle ilgili ayrıntıları yapılandırılmamış metinden çıkarmak için Person person = extractor.extractPerson(...) işlevini çağırabilir ve adı ve yaşı içeren bir Person örneği döndürebilirsiniz.

Örneği çalıştırın:

./gradlew run -q -DjavaMainClass=gemini.workshop.ExtractData

Aşağıdaki çıkışı göreceksiniz:

Anna
23

Evet, bu Ayşe ve 23 yaşında.

Bu AiServices yaklaşımıyla, güçlü şekilde yazılmış nesnelerle çalışırsınız. Doğrudan LLM ile etkileşime geçmiyorsunuz. Bunun yerine, ayıklanan kişisel bilgileri temsil eden Person kaydı gibi somut sınıflarla çalışırsınız ve Person örneği döndüren bir extractPerson() yöntemi içeren bir PersonExtractor nesnenize sahip olursunuz. LLM kavramı soyutlanır ve Java geliştiricisi olarak bu PersonExtractor arayüzünü kullandığınızda normal sınıflar ve nesnelerle işlem yaparsınız.

8. İstem şablonlarıyla istemleri yapılandırma

Yaygın bir talimat veya soru grubu kullanarak bir LLM ile etkileşime geçtiğinizde, istemin hiçbir zaman değişmeyen bir bölümü olur. Diğer bölümler ise verileri içerir. Örneğin, yemek tarifleri oluşturmak istiyorsanız "Yetenekli bir şefsiniz. Lütfen aşağıdaki malzemelerle bir yemek tarifi oluşturun: ..." gibi bir istem kullanabilir ve ardından malzemeleri bu metnin sonuna ekleyebilirsiniz. İstem şablonları, programlama dillerindeki kesikli dizelere benzer şekilde bu amaç için kullanılır. İstem şablonu, LLM'ye yapılan belirli bir çağrı için doğru verilerle değiştirebileceğiniz yer tutucular içerir.

Daha açık belirtmek gerekirse, app/src/main/java/gemini/workshop dizininde TemplatePrompt.java dosyasını inceleyelim:

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());
    }
}

Her zamanki gibi, VertexAiGeminiChatModel modelini yüksek sıcaklık ve yüksek en yüksek P ve en yüksek K değerleriyle yüksek düzeyde reklam öğesi oluşturacak şekilde yapılandırırsınız. Ardından, istemimizin dizesini ileterek from() statik yöntemiyle bir PromptTemplate oluşturun ve çift eğik çizgili yer tutucu değişkenleri ({{dish}} ve {{ingredients}}) kullanın.

Yer tutucunun adını ve bununla değiştirilecek dize değerini temsil eden bir anahtar/değer çiftleri haritası alan apply() işlevini çağırarak son istemi oluşturursunuz.

Son olarak, prompt.toUserMessage() talimatıyla bu istemden bir kullanıcı mesajı oluşturarak Gemini modelinin generate() yöntemini çağırırsınız.

Örneği çalıştırın:

./gradlew run -q -DjavaMainClass=gemini.workshop.TemplatePrompt

Oluşturulan çıkış şuna benzer şekilde görünür:

**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.

Haritadaki dish ve ingredients değerlerini, sıcaklığı (topK ve tokP) değiştirebilir ve kodu yeniden çalıştırabilirsiniz. Bu sayede, bu parametrelerin değiştirilmesinin LLM üzerindeki etkisini gözlemleyebilirsiniz.

İstem şablonları, LLM çağrıları için yeniden kullanılabilir ve parametrelenebilir talimatlar elde etmenin iyi bir yoludur. Kullanıcılarınız tarafından sağlanan verileri iletebilir ve istemleri farklı değerler için özelleştirebilirsiniz.

9. Çok görevli istemle metin sınıflandırma

LLM'ler metni farklı kategorilere ayırmada oldukça başarılıdır. Metinlere ve ilişkili kategorilerine dair bazı örnekler sağlayarak LLM'ye bu konuda yardımcı olabilirsiniz. Bu yaklaşıma genellikle çok görevli istem denir.

Belirli bir metin sınıflandırması türü olan yaklaşım analizi yapmak için app/src/main/java/gemini/workshop dizininde TextClassification.java dosyasını açalım.

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!"));
    }
}

Sentiment enum, bir duygunun farklı değerlerini (negatif, nötr veya pozitif) listeler.

main() yönteminde, Gemini sohbet modelini her zamanki gibi oluşturursunuz ancak yalnızca kısa bir yanıt istediğiniz için maksimum çıkış jetonu sayısını küçük tutarsınız: metin POSITIVE, NEGATIVE veya NEUTRAL olur. Modeli yalnızca bu değerleri döndürecek şekilde kısıtlamak için veri ayıklama bölümünde keşfettiğiniz yapılandırılmış çıkış desteğinden yararlanabilirsiniz. Bu nedenle responseSchema() yöntemi kullanılır. Bu kez, şema tanımını anlamak için SchemaHelper'teki kullanışlı yöntemi değil, Schema oluşturucuyu kullanacaksınız.

Model yapılandırıldıktan sonra, LangChain4j'ın AiServices işlevinin LLM'yi kullanarak sizin için uygulayacağı bir SentimentAnalysis arayüzü oluşturursunuz. Bu arayüzde bir yöntem bulunur: analyze(). Analiz edilecek metni giriş olarak alır ve bir Sentiment enum değeri döndürür. Bu nedenle, yalnızca tanınan duygu sınıfını temsil eden güçlü bir şekilde yazılmış bir nesneyi değiştirirsiniz.

Ardından, modeli sınıflandırma işlemini yapmaya teşvik etmek için "az sayıda örnek" vermek üzere bir sohbet belleği oluşturursunuz. Bu bellek, metni ve onunla ilişkili duyarlılığı temsil eden kullanıcı mesajı ve yapay zeka yanıtı çiftlerini iletir.

SentimentAnalysis arayüzümüz, kullanılacak model ve birkaç örnek içeren sohbet belleğini ileterek her şeyi AiServices.builder() yöntemiyle bir araya getirelim. Son olarak, analiz edilecek metni içeren analyze() yöntemini çağırın.

Örneği çalıştırın:

./gradlew run -q -DjavaMainClass=gemini.workshop.TextClassification

Tek bir kelime görürsünüz:

POSITIVE

Çilek sevmek olumlu bir duyguymuş.

10. Almayla Artırılmış Üretim

LLM'ler çok sayıda metinle eğitilir. Ancak bu bilgi, yalnızca eğitim sırasında gördüğü bilgileri kapsar. Model eğitimi için son tarihten sonra yayınlanan yeni bilgiler model tarafından kullanılamaz. Bu nedenle model, görmediği bilgilerle ilgili soruları yanıtlayamaz.

Bu nedenle, bu bölümde ele alınacak Arama Destekli Üretim (RAG) gibi yaklaşımlar, bir LLM'nin kullanıcılarının isteklerini yerine getirmek, daha güncel bilgilerle yanıt vermek veya eğitim sırasında erişilemeyen özel bilgilerle ilgili bilgi vermek için bilmesi gereken ek bilgileri sağlamaya yardımcı olur.

Görüşmelere dönelim. Bu kez, dokümanlarınızla ilgili soru sorabilirsiniz. Belgelerinizin daha küçük parçalara ("bölümlere") ayrıldığı bir veritabanından alakalı bilgileri alabilecek bir chatbot oluşturacaksınız. Bu bilgiler, model tarafından yalnızca eğitimde edinilen bilgilere güvenmek yerine yanıtlarını temellendirmek için kullanılır.

RAG'de iki aşama vardır:

  1. Besin alma aşaması: Dokümanlar belleğe yüklenir, daha küçük parçalara bölünür ve vektör yerleştirmeleri (parçaların yüksek boyutlu bir vektör gösterimi) hesaplanır ve anlamsal aramalar yapabilen bir vektör veritabanında saklanır. Bu besleme aşaması, genellikle doküman topluluğuna yeni dokümanlar eklenmesi gerektiğinde bir kez yapılır.

cd07d33d20ffa1c8.png

  1. Sorgu aşaması: Kullanıcılar artık dokümanlarla ilgili soru sorabilir. Soru da bir vektöre dönüştürülür ve veritabanındaki diğer tüm vektörlerle karşılaştırılır. En benzer vektörler genellikle anlamsal olarak ilişkilidir ve vektör veritabanı tarafından döndürülür. Ardından LLM'ye sohbetin bağlamı ve veritabanı tarafından döndürülen vektörlere karşılık gelen metin parçaları verilir ve bu parçalara bakarak yanıtını temellendirmesi istenir.

a1d2e2deb83c6d27.png

Belgelerinizi hazırlama

Bu yeni örnekte, yine hayali bir araba üreticisinin hayali bir araba modeli olan Cymbal Starlight hakkında sorular soracaksınız. Buradaki amaç, hayali bir arabayla ilgili bir dokümanın modelin bilgisinin bir parçası olmamasıdır. Dolayısıyla Gemini bu arabayla ilgili soruları doğru yanıtlayabiliyorsa RAG yaklaşımı işe yaramıştır: Gemini, belgenizde arama yapabilmektedir.

Chatbot'u uygulama

2 aşamalı yaklaşımın nasıl oluşturulacağını inceleyelim: Önce belge beslemesi, ardından kullanıcıların belgeyle ilgili soru sorduğu sorgu zamanı ("geri alma aşaması" olarak da bilinir).

Bu örnekte her iki aşama da aynı sınıfta uygulanmaktadır. Normalde, beslemeyi yapan bir uygulamanız ve kullanıcılarınıza sohbet robotu arayüzü sunan başka bir uygulamanız olur.

Ayrıca bu örnekte bellek içi bir vektör veritabanı kullanacağız. Gerçek bir üretim senaryosuna göre, besleme ve sorgulama aşamaları iki ayrı uygulamaya ayrılır ve vektörler bağımsız bir veritabanında kalıcıdır.

Doküman besleme

Belge besleme aşamasının ilk adımı, hayali arabamızla ilgili PDF dosyasını bulup okumak için bir PdfParser hazırlamaktır:

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());

Öncelikle normal sohbet dili modelini oluşturmak yerine bir yerleştirme modeli örneği oluşturursunuz. Bu, metin parçalarının (kelimeler, cümleler ve hatta paragraflar) vektör gösterimlerini oluşturma işlevi gören özel bir modeldir. Metin yanıtları yerine kayan nokta sayı vektörleri döndürür.

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();

Ardından, birlikte çalışarak aşağıdakileri yapabileceğiniz birkaç sınıfa ihtiyacınız olacak:

  • PDF dokümanlarını yükleyip parçalara bölün.
  • Bu parçaların tümü için vektör yerleştirmeleri oluşturun.
InMemoryEmbeddingStore<TextSegment> embeddingStore = 
    new InMemoryEmbeddingStore<>();

EmbeddingStoreIngestor storeIngestor = EmbeddingStoreIngestor.builder()
    .documentSplitter(DocumentSplitters.recursive(500, 100))
    .embeddingModel(embeddingModel)
    .embeddingStore(embeddingStore)
    .build();
storeIngestor.ingest(document);

Vektör yerleştirmelerini depolamak için bellek içi bir vektör veritabanı olan InMemoryEmbeddingStore örneği oluşturulur.

Belge, DocumentSplitters sınıfı sayesinde parçalara ayrılır. PDF dosyasının metnini 100 karakterlik çakışmalarla 500 karakterlik snippet'lere böler (kelimeleri veya cümleleri parçalara bölmemek için aşağıdaki parçayla).

Mağaza besleyici, belge ayırıcıyı, vektörleri hesaplamak için yerleştirme modelini ve bellek içi vektör veritabanını birbirine bağlar. Ardından, ingest() yöntemi beslemeyi gerçekleştirir.

İlk aşama tamamlandı. Belge, ilişkili vektör yerleşimleriyle metin parçalarına dönüştürüldü ve vektör veritabanında depolandı.

Soru sorma

Soru sormaya hazırlanın. Görüşmeyi başlatmak için bir sohbet modeli oluşturun:

ChatLanguageModel model = VertexAiGeminiChatModel.builder()
        .project(System.getenv("PROJECT_ID"))
        .location(System.getenv("LOCATION"))
        .modelName("gemini-1.5-flash-002")
        .maxOutputTokens(1000)
        .build();

Ayrıca, vektör veritabanını (embeddingStore değişkeninde) yerleştirme modeline bağlamak için bir alıcı sınıfına da ihtiyacınız vardır. Bu katmanın görevi, veritabanında benzer vektörleri bulmak için kullanıcının sorgusu için bir vektör gömme işlemi gerçekleştirerek vektör veritabanını sorgulamaktır:

EmbeddingStoreContentRetriever retriever =
    new EmbeddingStoreContentRetriever(embeddingStore, embeddingModel);

Bir araba uzmanı asistanını temsil eden bir arayüz oluşturun. Bu arayüz, AiServices sınıfının modelle etkileşime geçmeniz için uygulayacağı bir arayüzdür:

interface CarExpert {
    Result<String> ask(String question);
}

CarExpert arayüzü, LangChain4j'ın Result sınıfına sarmalanmış bir dize yanıtı döndürür. Bu sarmalayıcıyı neden kullanmalısınız? Çünkü bu yöntem yalnızca size yanıtı vermekle kalmaz, aynı zamanda içerik alıcı tarafından döndürülen veritabanı parçalarını incelemenize de olanak tanır. Böylece, son yanıtı temellendirmek için kullanılan dokümanların kaynaklarını kullanıcıya gösterebilirsiniz.

Bu noktada yeni bir yapay zeka hizmeti yapılandırabilirsiniz:

CarExpert expert = AiServices.builder(CarExpert.class)
    .chatLanguageModel(model)
    .chatMemory(MessageWindowChatMemory.withMaxMessages(10))
    .contentRetriever(retriever)
    .build();

Bu hizmet aşağıdakileri birbirine bağlar:

  • Daha önce yapılandırdığınız sohbet dili modeli.
  • Görüşmeyi takip etmek için sohbet geçmişi.
  • retriever, bir vektör yerleştirme sorgusunu veritabanındaki vektörlerle karşılaştırır.
.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())

Artık sorularınızı sormaya hazırsınız.

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());
});

Kaynak kodun tamamı app/src/main/java/gemini/workshop dizininde RAG.java dosyasındadır.

Örneği çalıştırın:

./gradlew -q run -DjavaMainClass=gemini.workshop.RAG

Çıktıda, sorularınıza verilen yanıtları görürsünüz:

=== 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. İşlev çağırma

Bilgi alan veya işlem yapan uzak web API'leri ya da bir tür hesaplama yapan hizmetler gibi harici sistemlere LLM'nin erişmesini istediğiniz durumlar vardır. Örneğin:

Uzak web API'leri:

  • Müşteri siparişlerini takip etme ve güncelleme.
  • Bir sorun izleyicide destek kaydı bulun veya oluşturun.
  • Hisse senedi fiyatları veya IoT sensör ölçümleri gibi gerçek zamanlı verileri getirme.
  • E-posta gönderin.

Hesaplama araçları:

  • Daha gelişmiş matematik problemleri için bir hesap makinesi.
  • LLM'lerin mantık yürütme mantığına ihtiyaç duyduğu durumlarda kodu çalıştırmak için kod yorumlama.
  • LLM'nin veritabanı sorgulayabilmesi için doğal dil isteklerini SQL sorgularına dönüştürün.

İşlev çağrısı (bazen araçlar veya araç kullanımı olarak da adlandırılır), modelin kullanıcının istemlerini daha güncel verilerle doğru şekilde yanıtlayabilmesi için kendi adına bir veya daha fazla işlev çağrısı yapılmasını istemesidir.

Kullanıcıdan gelen belirli bir istem ve bu bağlamla alakalı olabilecek mevcut işlevler hakkında bilgi verildiğinde LLM, işlev çağrısı isteğiyle yanıt verebilir. LLM'yi entegre eden uygulama, işlevi kendi adına çağırabilir ve ardından LLM'ye bir yanıtla yanıt verebilir. LLM de metin biçiminde bir yanıtla yanıt vererek yorumu geri gönderir.

İşlev çağırmayla ilgili dört adım

İşlev çağırma örneğine göz atalım: Hava durumu tahmini hakkında bilgi edinme.

Gemini'ye veya başka bir LLM'ye Paris'teki hava durumu hakkında soru sorarsanız mevcut hava durumu tahmini hakkında bilgi sahibi olmadığını söyler. LLM'nin hava durumu verilerine anlık erişmesini istiyorsanız kullanılmasını isteyebileceği bazı işlevleri tanımlamanız gerekir.

Aşağıdaki şemaya göz atın:

31e0c2aba5e6f21c.png

1️⃣ Öncelikle bir kullanıcı Paris'te hava durumunu sorar. LangChain4j kullanan chatbot uygulaması, LLM'nin sorguyu yerine getirmesine yardımcı olmak için kullanabileceği bir veya daha fazla işlev olduğunu bilir. Sohbet robotu hem ilk istemi hem de çağrılabilecek işlevlerin listesini gönderir. Burada, konum için bir dize parametresi alan getWeather() adlı bir işlev gösterilmektedir.

8863be53a73c4a70.png

LLM, hava durumu tahminlerini bilmediği için metinle yanıt vermek yerine işlev yürütme isteği gönderir. Sohbet robotu, konum parametresi olarak "Paris" ile getWeather() işlevini çağırmalıdır.

d1367cc69c07b14d.png

2️⃣ Sohbet robotu, LLM adına bu işlevi çağırır ve işlev yanıtını alır. Burada yanıtın {"forecast": "sunny"} olduğunu varsayalım.

73a5f2ed19f47d8.png

3️⃣ Sohbet robotu uygulaması, JSON yanıtını LLM'ye geri gönderir.

20832cb1ee6fbfeb.png

4️⃣ LLM, JSON yanıtını inceler, bu bilgileri yorumlar ve sonunda Paris'te hava durumunun güneşli olduğunu belirten metni döndürür.

Her adım kod olarak

Öncelikle Gemini modelini her zamanki gibi yapılandırın:

ChatLanguageModel model = VertexAiGeminiChatModel.builder()
    .project(System.getenv("PROJECT_ID"))
    .location(System.getenv("LOCATION"))
    .modelName("gemini-1.5-flash-002")
    .maxOutputTokens(100)
    .build();

Çağırılabilecek işlevi açıklayan bir araç spesifikasyonu tanımlarsınız:

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();

İşlevin adı ve parametrenin adı ve türü tanımlanır ancak hem işleve hem de parametrelere açıklama verilir. Açıklamalar çok önemlidir ve LLM'nin bir işlevin ne yapabileceğini gerçekten anlamasına ve böylece bu işlevin sohbet bağlamında çağrılıp çağrılmaması gerektiğine karar vermesine yardımcı olur.

1. adıma başlayarak Paris'teki hava durumuyla ilgili ilk soruyu gönderelim:

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. adımda, modelin kullanmasını istediğimiz aracı iletiriz ve model bir araç yürütme isteğiyle yanıt verir:

// 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. adım. Bu noktada, LLM'nin hangi işlevi çağırmamızı istediğini biliyoruz. Kodda, harici bir API'ye gerçek bir çağrı göndermiyoruz. Doğrudan varsayımsal bir hava durumu tahmini döndürüyoruz:

// 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. adımda LLM, işlev yürütme sonucu hakkında bilgi edinir ve ardından metin biçiminde bir yanıt sentezleyebilir:

// 4) The model answers with a sentence describing the weather
Response<AiMessage> weatherResponse = model.generate(allMessages);
System.out.println("Answer: " + weatherResponse.content().text());

Çıkış:

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.

Araç yürütme isteğinin ve yanıtın üst kısmındaki çıkışta görebilirsiniz.

Kaynak kodun tamamı app/src/main/java/gemini/workshop dizininde FunctionCalling.java altındadır:

Örneği çalıştırın:

./gradlew run -q -DjavaMainClass=gemini.workshop.FunctionCalling

Aşağıdakine benzer bir çıkış görürsünüz:

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, işlev çağrılarını yönetir.

Önceki adımda, normal metin soru/yanıt ve işlev isteği/yanıt etkileşimlerinin nasıl iç içe yerleştirildiğini gördünüz ve bu etkileşimlerin arasında gerçek bir işlevi çağırmadan doğrudan istenen işlev yanıtını sağladınız.

Ancak LangChain4j, sohbeti her zamanki gibi yönetirken işlev çağrılarını sizin için şeffaf bir şekilde yönetebilecek daha üst düzey bir soyutlama da sunar.

Tek işlev çağrısı

FunctionCallingAssistant.java öğesini parça parça inceleyelim.

Öncelikle, işlevin yanıt veri yapısını temsil edecek bir kayıt oluşturursunuz:

record WeatherForecast(String location, String forecast, int temperature) {}

Yanıt, konum, tahmin ve sıcaklık hakkında bilgi içerir.

Ardından, modelde kullanılmasını istediğiniz gerçek işlevi içeren bir sınıf oluşturursunuz:

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);
        }
    }
}

Bu sınıfın tek bir işlev içerdiğini ancak modelin çağırmayı isteyebileceği işlevin açıklamasına karşılık gelen @Tool ek açıklamayla not edildiğini unutmayın.

İşlevin parametreleri (burada tek bir parametre) de ek açıklamayla belirtilir. Bu kısa @P ek açıklaması, parametrenin açıklamasını da içerir. Daha karmaşık senaryolar için modelin kullanabileceği istediğiniz kadar işlev ekleyebilirsiniz.

Bu sınıfta bazı hazır yanıtlar döndürürsünüz ancak gerçek bir harici hava durumu tahmini hizmetini çağırmak isterseniz bu hizmete çağrıyı bu yöntemin gövdesinde yaparsınız.

Önceki yaklaşımda bir ToolSpecification oluşturduğunuzda gördüğümüz gibi, bir işlevin ne yaptığını belgelemek ve parametrelerin neyle eşleştiğini açıklamak önemlidir. Bu, modelin bu işlevin nasıl ve ne zaman kullanılabileceğini anlamasına yardımcı olur.

Ardından LangChain4j, modelle etkileşimde bulunmak için kullanmak istediğiniz sözleşmeye karşılık gelen bir arayüz sağlamanıza olanak tanır. Burada, kullanıcı mesajını temsil eden bir dize alan ve modelin yanıtına karşılık gelen bir dize döndüren basit bir arayüz vardır:

interface WeatherAssistant {
    String chat(String userMessage);
}

Daha karmaşık nesneler, tüketilen jeton sayısı gibi ek bilgiler de içerdiğinden, daha gelişmiş durumları ele almak istiyorsanız LangChain4j'ın UserMessage (kullanıcı mesajı için) veya AiMessage (model yanıtı için) ya da hatta TokenStream içeren daha karmaşık imzalar da kullanabilirsiniz. Ancak basitlik açısından, girişte yalnızca dize ve çıkışta da dize alacağız.

Tüm parçaları bir araya getiren main() yöntemiyle bitirelim:

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?"));
}

Her zamanki gibi Gemini Chat modelini yapılandırırsınız. Ardından, modelin çağırmamızı isteyeceği "işlevi" içeren hava durumu tahmini hizmetinizi örneklendirirsiniz.

Ardından, sohbet modelini, sohbet belleğini ve aracı (ör. işleviyle birlikte hava durumu tahmini hizmeti) bağlamak için AiServices sınıfını tekrar kullanırsınız. AiServices, tanımladığınız WeatherAssistant arayüzünü uygulayan bir nesne döndürür. Geriye kalan tek şey, ilgili asistanın chat() yöntemini çağırmaktır. Bu işlevi çağırırken yalnızca metin yanıtlarını görürsünüz. Ancak işlev çağrısı istekleri ve işlev çağrısı yanıtları geliştirici tarafından görülemez ve bu istekler otomatik olarak ve şeffaf bir şekilde işlenir. Gemini, bir işlevin çağrılması gerektiğini düşünürse işlev çağrısı isteğiyle yanıt verir ve LangChain4j, yerel işlevi sizin adınıza çağırır.

Örneği çalıştırın:

./gradlew run -q -DjavaMainClass=gemini.workshop.FunctionCallingAssistant

Aşağıdakine benzer bir çıkış görürsünüz:

OK. The weather in Paris is sunny with a temperature of 20 degrees.

Bu, tek bir işleve örnektir.

Birden fazla işlev çağrısı

Birden fazla işleviniz de olabilir ve LangChain4j'in sizin adınıza birden fazla işlev çağrısını yönetmesine izin verebilirsiniz. Birden fazla işlev içeren bir örnek için MultiFunctionCallingAssistant.java bölümüne göz atın.

Para birimlerini dönüştürme işlevi vardır:

@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;
}

Bir hissenin değerini almak için kullanılabilecek başka bir işlev:

@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;
}

Belirli bir tutara yüzde uygulamak için kullanabileceğiniz başka bir işlev:

@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;
}

Ardından tüm bu işlevleri ve bir MultiTools sınıfını birleştirerek "AAPL hisse fiyatının% 10'u, ABD dolarından avroya dönüştürüldüğünde ne olur?" gibi sorular sorabilirsiniz.

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?"));
}

Aşağıdaki şekilde çalıştırın:

./gradlew run -q -DjavaMainClass=gemini.workshop.MultiFunctionCallingAssistant

Aşağıdaki adlara sahip birden fazla işlev görürsünüz:

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.

Temsilcilere Yönelik

İşlev çağrısı, Gemini gibi büyük dil modelleri için mükemmel bir uzantı mekanizmasıdır. Bu sayede genellikle "temsilciler" veya "yapay zeka asistanları" olarak adlandırılan daha karmaşık sistemler oluşturabiliyoruz. Bu aracılar, harici API'ler aracılığıyla harici dünyayla ve harici ortamda yan etkileri olabilecek hizmetlerle (ör. e-posta gönderme, destek kaydı oluşturma vb.) etkileşim kurabilir.

Bu tür güçlü aracılar oluştururken bunu sorumlu bir şekilde yapmanız gerekir. Otomatik işlemler yapmadan önce bir kişinin sürece dahil olmasını göz önünde bulundurmalısınız. Dış dünyayla etkileşime geçen LLM destekli aracılar tasarlarken güvenliği göz önünde bulundurmanız önemlidir.

13. Gemma'yı Ollama ve TestContainers ile çalıştırma

Şimdiye kadar Gemini'yi kullanıyorduk ancak küçük kardeşi Gemma da var.

Gemma, Gemini modellerini oluşturmak için kullanılan aynı araştırma ve teknolojiden oluşturulmuş, hafif ve son teknoloji açık modellerden oluşan bir ailedir. Gemma, Gemma1 ve Gemma2 olmak üzere iki varyantta ve her biri farklı boyutlarda mevcuttur. Gemma1 iki boyutta mevcuttur: 2B ve 7B. Gemma2, 9B ve 27B olmak üzere iki boyutta mevcuttur. Bu modellerin ağırlıkları ücretsiz olarak kullanılabilir. Küçük boyutları sayesinde bunları dizüstü bilgisayarınızda veya Cloud Shell'de bile kendi başınıza çalıştırabilirsiniz.

Gemma'yı nasıl çalıştırıyorsunuz?

Gemma'yı çalıştırmanın birçok yolu vardır: bulutta, Vertex AI üzerinden bir düğmeye tıklayarak veya bazı GPU'larla GKE üzerinden. Ayrıca yerel olarak da çalıştırabilirsiniz.

Gemma'yı yerel olarak çalıştırmak için kullanabileceğiniz iyi bir seçenek, Llama 2 ve Mistral gibi küçük modelleri yerel makinenizde çalıştırmanıza olanak tanıyan Ollama'dır. Docker'a benzer ancak LLM'ler içindir.

İşletim sisteminizle ilgili talimatları uygulayarak Ollama'yı yükleyin.

Linux ortamını kullanıyorsanız Ollama'yı yükledikten sonra etkinleştirmeniz gerekir.

ollama serve > /dev/null 2>&1 & 

Yerel olarak yüklendikten sonra bir modeli almak için komutlar çalıştırabilirsiniz:

ollama pull gemma:2b

Modelin çekilmesini bekleyin. Bu işlem biraz zaman alabilir.

Modeli çalıştırın:

ollama run gemma:2b

Artık modelle etkileşim kurabilirsiniz:

>>> Hello!
Hello! It's nice to hear from you. What can I do for you today?

İstemden çıkmak için Ctrl+D tuşlarına basın.

TestContainers'da Ollama'da Gemma'yı çalıştırma

Ollama'yı yerel olarak yükleyip çalıştırmak yerine TestContainers tarafından yönetilen bir kapsayıcı içinde kullanabilirsiniz.

TestContainers yalnızca test için değil, kapsayıcıları çalıştırmak için de kullanılabilir. Hatta yararlanabileceğiniz özel bir OllamaContainer de var.

Tüm resmi görmek için:

2382c05a48708dfd.png

Uygulama

GemmaWithOllamaContainer.java öğesini parça parça inceleyelim.

Öncelikle, Gemma modelini alan türetilmiş bir Ollama kapsayıcısı oluşturmanız gerekir. Bu resim, önceki bir çalıştırmadan zaten mevcuttur veya oluşturulur. Resim zaten mevcutsa TestContainers'a varsayılan Ollama resmini Gemma destekli varyantınızla değiştirmek istediğinizi belirtmeniz yeterlidir:

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"));
    }
}

Ardından, Ollama test kapsayıcısını oluşturup başlatın ve kullanmak istediğiniz modelin bulunduğu kapsayıcının adresini ve bağlantı noktasını belirterek bir Ollama sohbet modeli oluşturun. Son olarak, model.generate(yourPrompt) işlevini her zamanki gibi çağırırsınız:

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);
}

Aşağıdaki şekilde çalıştırın:

./gradlew run -q -DjavaMainClass=gemini.workshop.GemmaWithOllamaContainer

İlk çalıştırma işleminde kapsayıcının oluşturulması ve çalıştırılması biraz zaman alır. Ancak bu işlem tamamlandığında Gemma'nın yanıt verdiğini görürsünüz:

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.

Gemma, Cloud Shell'de çalışıyor.

14. Tebrikler

Tebrikler, LangChain4j ve Gemini API'yi kullanarak Java'da ilk üretken yapay zeka sohbet uygulamanızı başarıyla oluşturdunuz. Bu süreçte, çok modlu büyük dil modellerinin oldukça güçlü olduğunu ve kendi dokümanlarınızda bile soru/yanıt verme, veri ayıklama, harici API'lerle etkileşim kurma ve daha pek çok görevi yerine getirebildiğini keşfettiniz.

Sırada ne var?

Uygulamalarınızı güçlü LLM entegrasyonlarıyla geliştirme sırası sizde!

Daha fazla bilgi

Referans dokümanları